Objects and Interfaces

beginner
11 min

Objects and Interfaces

Most real data is structured: users, products, API responses. TypeScript describes the shape of objects with object types and, most commonly, with interfaces. In this lesson you will learn how to define interfaces, use optional and readonly properties, nest objects, extend interfaces, and add index signatures for dynamic keys.

Inline Object Types

You can annotate an object shape directly:

typescript
function printUser(user: { name: string; age: number }) { console.log(user.name + " is " + user.age); }

This works, but repeating the shape everywhere gets messy. Named types fix that.

Defining an Interface

An interface gives an object shape a reusable name:

typescript
interface User { id: number; name: string; email: string; } const ada: User = { id: 1, name: "Ada Lovelace", email: "ada@example.com", };

If you omit a property or add a misspelled one, the compiler complains immediately. TypeScript uses structural typing: any object with the right properties satisfies the interface, regardless of how it was created. There is no need to "implement" or register anything.

Optional and Readonly Properties

Two modifiers make interfaces much more expressive:

typescript
interface Profile { readonly id: number; // cannot be reassigned after creation displayName: string; bio?: string; // may be absent } const p: Profile = { id: 7, displayName: "Grace" }; p.displayName = "Grace Hopper"; // OK p.id = 8; // Error: id is readonly // bio is string | undefined, so check before use console.log(p.bio?.length ?? 0);

readonly is a compile-time guarantee only — it does not freeze the object at runtime — but it documents intent and prevents accidental mutation in your own code.

Nested Objects and Arrays of Objects

Interfaces compose naturally:

typescript
interface Address { city: string; country: string; } interface Customer { name: string; address: Address; orders: { sku: string; quantity: number }[]; }

Prefer extracting nested shapes into their own named interfaces once they are used in more than one place — it keeps error messages readable.

Extending Interfaces

Interfaces can build on each other with extends, which models "is-a" relationships without classes:

typescript
interface Person { name: string; } interface Employee extends Person { employeeId: number; department: string; } const emp: Employee = { name: "Lin", employeeId: 42, department: "Platform" };

An interface can extend multiple parents, merging all their members.

Index Signatures for Dynamic Keys

Sometimes you do not know property names ahead of time — think of a dictionary of scores keyed by username:

typescript
interface ScoreBoard { [username: string]: number; } const scores: ScoreBoard = { ada: 10, grace: 12 }; scores["linus"] = 9;

Every property you access is typed as number. For stricter alternatives, the built-in Record<string, number> utility type does the same job — we cover it in the utility types lesson.

Excess Property Checks

One subtlety: when you pass a fresh object literal directly, TypeScript checks for extra properties too:

typescript
printUser({ name: "Ada", age: 36, role: "admin" }); // Error: role does not exist

This catches typos like emial instead of email. Assigning the object to a typed variable first relaxes the check.

Key Takeaways

  • Interfaces name object shapes; TypeScript matches them structurally.
  • ? marks optional properties; readonly prevents reassignment at compile time.
  • Use extends to compose interfaces and avoid duplication.
  • Index signatures type objects with dynamic keys.
  • Fresh object literals get excess property checks, catching typos early.

Next lesson: Union Types, Intersection Types and Type Narrowing — modeling values that can be one of several shapes.