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.
You can annotate an object shape directly:
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.
An interface gives an object shape a reusable name:
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.
Two modifiers make interfaces much more expressive:
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.
Interfaces compose naturally:
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.
Interfaces can build on each other with extends, which models "is-a" relationships without classes:
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.
Sometimes you do not know property names ahead of time — think of a dictionary of scores keyed by username:
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.
One subtlety: when you pass a fresh object literal directly, TypeScript checks for extra properties too:
printUser({ name: "Ada", age: 36, role: "admin" }); // Error: role does not existThis catches typos like emial instead of email. Assigning the object to a typed variable first relaxes the check.
? marks optional properties; readonly prevents reassignment at compile time.extends to compose interfaces and avoid duplication.Next lesson: Union Types, Intersection Types and Type Narrowing — modeling values that can be one of several shapes.