How to extend an Interface in TypeScript

avatar

Borislav Hadzhiev

Tue Feb 15 20224 min read

banner

Photo by Max Libertine

Extending Interfaces in TypeScript #

Use the extends keyword to extend interfaces in TypeScript, e.g. interface Dog extends Animal {age: number;}. The extends keyword allows us to copy the members from other named types and add new members to the final, more generic interface.

index.ts
interface Animal { name: string; age: number; } interface Dog extends Animal { run(): void; } const a1: Dog = { name: 'Tom', age: 3, run() { console.log('the dog runs...'); }, };

The extends keyword removes the need of having to repeat the members of other types at multiple places and shows the relation between the interfaces to the reader of the code.

You can extend from multiple interfaces by separating them with a comma.

index.js
interface Dog { name: string; } interface Shepherd { guardian: boolean; } interface Animal extends Dog, Shepherd { age: number; } const a1: Animal = { name: 'Tom', guardian: true, age: 3, };

The main benefits of extending an interface are:

  1. Reducing duplication, because we don't have to copy properties between interfaces.
  2. Signaling intent to the reader of our code that there is a relation between the types.

You are not required to add any new members to the final interface and can use the extends keyword to simply combine interfaces.

index.ts
interface Dog { name: string; } interface Shepherd { guardian: boolean; } // 👇️ Combine the Dog and Shepherd interfaces interface Animal extends Dog, Shepherd {} // 👇️ Alternatively use intersection type type Animal2 = Dog & Shepherd; const a1: Animal = { name: 'Tom', guardian: true, };

You can also use the extends keyword to extend an interface with existing type aliases.

index.ts
type Dog = { name: string; age: number; }; interface Shepherd { guardian: boolean; } interface Animal extends Dog, Shepherd {} const a1: Animal = { name: 'Tom', age: 3, guardian: true, };

The main reason to extend an interface is that it signals to the reader of the code that the types we are combining are related in some way.

If we just repeat the fields between the interfaces, the connection is lost.

An easy way to think about the extends keyword is - we are copying members from other more specific, named types and adding any new members we want to construct a final more generic interface.

If you need to override a property from the interface you extend, you can use the Omit utility type.

index.ts
interface Employee { id: number; name: string; salary: number; } // for multiple use - Omit<Employee, 'id' | 'salary'> interface Developer extends Omit<Employee, 'id'> { id: string; // 👈️ override type of id language: string; } const dev: Developer = { id: 'dev-1', name: 'Tom', salary: 100, language: 'TypeScript', };

The Omit utility type constructs a new type by picking the properties from the provided type and removing the specified keys.

In the example, we removed the id property from the Employee type when extending from it, so we can override its type.

Had we not removed the property and tried to specify a different type in the Developer interface, we would have gotten an error.

index.ts
interface Employee { id: number; name: string; salary: number; } // ⛔️ Error: Interface 'Developer' incorrectly extends interface 'Employee'. // Types of property 'id' are incompatible. // Type 'string' is not assignable to type 'number'.ts(2430) interface Developer extends Employee { id: string; // 👈️ override type of id language: string; }
TypeScript is telling us we can't extend from an interface and use the same property name with a different, incompatible type.

The best way to get around this is to remove the type when extending from the interface and override it in the new interface.

You can use a union of string literals to omit multiple properties from a type when extending.

index.ts
interface Employee { id: number; name: string; salary: number; } // 👇️ omit `id` and `salary` interface Developer extends Omit<Employee, 'id' | 'salary'> { id: string; // 👈️ override type of id salary: string; // 👈️ override type of salary language: string; } const dev: Developer = { id: 'dev-1', name: 'Tom', salary: '50 K', language: 'TypeScript', };

To pass multiple properties to the the Omit utility type, separate the properties with a pipe |.

The same approach can be used to remove some properties from a type when extending - omit the properties and don't declare them on the new interface.

Note that when extending from an interface, you can provide a more narrow type for the same property name.

index.ts
interface Employee { id: number | string; // 👈️ number OR string (wide) name: string; salary: number; } interface Developer extends Employee { id: string; // 👈️ only string (narrow) language: string; } const dev: Developer = { id: 'dev-1', // 👈️ can only be string now name: 'Tom', salary: 100, language: 'TypeScript', };

The id property in the Employee interface has a type of number or string, but when extending from the interface, we specified a more narrow type of string.

The id property in the Developer interface can only be of type string.

Overriding the type of a property with a more specific one is allowed, however, you can't provide a type that's not compatible. If you need to do that, you have to use the Omit utility type to remove the specific property and override its type.

You can use the implements clause to enforce that a class satisfies one or more interfaces.

index.ts
interface Dog { name: string; run(): void; } interface Shepherd extends Dog { guardian: boolean; } // ⛔ ️Error: Class 'Animal' incorrectly implements interface 'Shepherd'. // Type 'Animal' is missing the following properties // from type 'Shepherd': guardian, name, runts(2420) class Animal implements Shepherd {}

The example shows that we get an error, because the class fails to correctly implement the interface.

To solve the error, make sure to correctly implement the interface in the class.

index.ts
interface Dog { name: string; run(): void; } interface Shepherd extends Dog { guardian: boolean; } class Animal implements Shepherd { name = 'Tom'; guardian = true; run() { console.log('The animal runs'); } }
Use the search field on my Home Page to filter through my more than 1,000 articles.