Course outline · 0% complete

0/26 lessons0%

Course overview →

Extending Shapes, and type vs interface

lesson 3-3 · ~12 min · 10/26

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.

Animalname: stringextendsDogname: string (inherited)breed: string
extends copies every property of the parent interface into the child, then the child adds its own.

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 rex literal must supply both properties. Omitting name errors with: Property 'name' is missing in type '{ breed: string; }' but required in type 'Dog'. Inherited properties are just as required as declared ones.
  • Point behaves identically to an equivalent interface at the use site. home.x is a number either way, which is why the two keywords are interchangeable for plain object shapes.
  • Both objects are ordinary JavaScript objects at runtime. extends on 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 naminginterfacetype
An object shapeYesYes
A shape built on another shapeYes, with extendsYes, with &
A union such as string | numberNoYes
A literal union such as "on" | "off"NoYes

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 wheels

Reading the inheritance

  • The extends keyword goes right in the interface header: interface Car extends Vehicle { ... }. Everything in Vehicle is now part of Car.
  • c.wheels is available on a Car because it was inherited, even though the Car body never mentions it. The object literal must supply it too, which is why the call passes both brand and wheels.
  • Add a Motorcycle extends Vehicle later and it picks up wheels from 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.