Building shapes from shapes
Real systems model families of related shapes: every product listing has a title and a price, but a book also has an author and a shirt also has a size. Copy the shared properties into each interface by hand and the copies drift apart the first time someone edits only one of them. extends exists so shared structure is declared once and inherited everywhere.
Interfaces can extend other interfaces, inheriting all their properties:
interface Animal { name: string; } interface Dog extends Animal { breed: string; }
A Dog has both name and breed. This mirrors how you layered behavior with classes and prototypes in Advanced JavaScript, but purely at the type level.
There is a second way to name a type, the type alias:
type Point = { x: number; y: number };
For object shapes, type and interface are nearly interchangeable. The practical differences: interface can be extended and reopened, while type can also name things that are not objects at all, like unions (type Id = string | number), which interfaces cannot.
Inheritance and a type alias side by side
Dog gets name from Animal via extends, and Point is declared with a type alias instead of an interface.
interface Animal { name: string; } interface Dog extends Animal { breed: string; } type Point = { x: number; y: number }; const rex: Dog = { name: "Rex", breed: "husky" }; const home: Point = { x: 0, y: 0 }; console.log(rex.name + " the " + rex.breed); console.log("home: (" + home.x + ", " + home.y + ")");
Output
Rex the husky home: (0, 0)
What each declaration requires
- The
rexliteral must supply both properties. Omittingnameerrors with:Property 'name' is missing in type '{ breed: string; }' but required in type 'Dog'. Inherited properties are just as required as declared ones. Pointbehaves identically to an equivalent interface at the use site.home.xis anumbereither way, which is why the two keywords are interchangeable for plain object shapes.- Both objects are ordinary JavaScript objects at runtime.
extendson an interface creates no prototype chain and no class, it only combines type information.
The one thing only a type alias can name
The type that requires a type alias is a union of non-object types, such as type Id = string | number.
Interfaces describe object shapes, so anything shaped like an object works with either keyword. An object type, an object type extended from another, and a method signature can all be written as an interface or as a type.
A union like string | number is not an object shape at all. There are no properties to list, so there is nothing for an interface body to contain, and only a type alias can give it a name. This is the most common reason real codebases reach for type.
| What you are naming | interface | type |
|---|---|---|
| An object shape | Yes | Yes |
| A shape built on another shape | Yes, with extends | Yes, with & |
A union such as string | number | No | Yes |
A literal union such as "on" | "off" | No | Yes |
describeCar: inheriting a property through extends
Vehicle holds the property every vehicle shares, and Car adds what is specific to cars.
interface Vehicle { wheels: number; } interface Car extends Vehicle { brand: string; } function describeCar(c: Car): string { return c.brand + " with " + c.wheels + " wheels"; } console.log(describeCar({ brand: "Volvo", wheels: 4 }));
Output
Volvo with 4 wheelsReading the inheritance
- The
extendskeyword goes right in the interface header:interface Car extends Vehicle { ... }. Everything inVehicleis now part ofCar. c.wheelsis available on aCarbecause it was inherited, even though theCarbody never mentions it. The object literal must supply it too, which is why the call passes bothbrandandwheels.- Add a
Motorcycle extends Vehiclelater and it picks upwheelsfrom the same single declaration. That is the payoff: the shared property has exactly one home.
Combining shapes with &
The | of Unit 2 said "one of these". Its counterpart, the intersection type &, says "all of these at once":
type Named = { name: string }; type Timestamped = { createdAt: number }; type Post = Named & Timestamped & { body: string };
A Post must carry every property of every part: name, createdAt, and body.
This is the type-alias counterpart of extends, and real codebases use it to bolt shared fields such as ids, timestamps, and audit info onto many shapes without repeating them.
One caution. If two parts declare the same property with incompatible types, no value can satisfy both, so the property becomes impossible to construct. Keep the parts disjoint.
What an intersection requires
Given type A = { x: number } and type B = { y: string }, a value of type A & B must contain both properties: an x that is a number and a y that is a string.
Intersection means the value satisfies both types at once, so it needs every property of A together with every property of B. An object with only x fails, and so does an object with only y.
Contrast that with A | B, the union, where matching one member is enough. The two symbols pull in opposite directions, and the arithmetic intuition is backwards from what the names suggest: adding more types to an intersection makes the requirements stricter, while adding more types to a union makes them looser.
On object shapes, & is how type aliases express what interfaces express with extends.