Skip to content
TypeScript ts interfaces 3 min read

Extending Interfaces

Interfaces compose through inheritance: the extends keyword lets one interface build on the members of one or more others. This keeps related shapes DRY, models “is-a” relationships, and lets you assemble large contracts from small, focused pieces. An interface can extend multiple bases at once, can extend classes, and can even refine inherited members to narrower types. This page covers single and multiple inheritance, extending classes, member overriding, and how extends compares to intersection types.

Extending a single interface

Use extends to inherit all members of a base interface, then add new ones. The derived interface requires every member from both.

interface Animal {
  name: string;
  age: number;
}

interface Dog extends Animal {
  breed: string;
  bark(): void;
}

const rex: Dog = {
  name: "Rex",
  age: 3,
  breed: "Labrador",
  bark() {
    console.log("Woof");
  },
};

Dog carries name and age from Animal plus its own breed and bark. A Dog is therefore assignable wherever an Animal is expected, since it has at least the base’s members.

Extending multiple interfaces

An interface can extend several bases at once by listing them comma-separated. The result combines all their members, which is a clean way to mix in independent capability contracts.

interface Timestamped {
  createdAt: Date;
  updatedAt: Date;
}

interface SoftDeletable {
  deletedAt: Date | null;
}

interface Entity extends Timestamped, SoftDeletable {
  id: string;
}

const record: Entity = {
  id: "1",
  createdAt: new Date(),
  updatedAt: new Date(),
  deletedAt: null,
};

Entity must satisfy the union of all three contracts. If two bases declare the same property with incompatible types, the compiler reports a conflict and you must reconcile them in the derived interface.

Extending a class

An interface can extend a class, in which case it inherits the class’s instance member types (including private and protected members). This is occasionally useful for typing objects that must be structurally compatible with a class’s shape.

class Control {
  protected state: boolean = false;
}

interface SelectableControl extends Control {
  select(): void;
}

Because SelectableControl inherits the protected state, only Control or its subclasses can produce a value assignable to SelectableControl. Use this sparingly; it tightly couples the interface to a class implementation.

Overriding inherited members

A derived interface may redeclare an inherited member with a narrower, compatible type — useful for specializing a generic base. The override must be assignable to the original; you cannot widen or change it to an unrelated type.

interface Shape {
  kind: string;
}

interface Circle extends Shape {
  kind: "circle"; // narrowed from string to a literal
  radius: number;
}

// ❌ Error: Interface 'Bad' incorrectly extends 'Shape'.
//    Types of property 'kind' are incompatible.
interface Bad extends Shape {
  kind: number;
}

Narrowing kind to the literal "circle" makes Circle work as a discriminated union member while still satisfying Shape.

extends vs intersection types

Type aliases achieve a similar combination using the & intersection operator. The two are close but differ in error reporting and conflict handling, summarized below.

// interface inheritance
interface C extends A, B {}

// type intersection
type C2 = A & B;
Aspectinterface extendstype intersection (&)
Combines membersYesYes
Conflicting membersCompile error at declarationSilently produces never for the property
Can extend a classYesNo
Works with unionsBase must be an object typeCan intersect anything
Error messagesCaught early, clearerSurfaced later at use site

Prefer extends for plain object hierarchies — conflicts are reported immediately and the relationship reads clearly. Reach for & when combining with unions, mapped, or conditional types that interfaces cannot express.

Best Practices

  • Use extends to model genuine “is-a” relationships and to share common members.
  • Compose multiple small capability interfaces instead of one monolithic interface.
  • Narrow inherited members to literals when building discriminated unions.
  • Keep base interfaces focused so they remain reusable across many derived types.
  • Avoid extending classes unless you specifically need structural compatibility.
  • Prefer extends over & for object hierarchies to get early conflict detection.
  • Watch for incompatible members across multiple bases; resolve them in the child interface.
Last updated June 29, 2026
Was this helpful?