How to Extend one or Multiple Interfaces in TypeScript

avatar
Borislav Hadzhiev

Last updated: Feb 29, 2024
9 min

banner

# Table of Contents

  1. Extend an interface in TypeScript
  2. Extend Multiple interfaces in TypeScript
  3. Extending an interface with an existing Type Alias
  4. Extend an Interface excluding a Property in TypeScript
  5. Overriding a property from an interface while extending it
  6. Omit multiple properties before extending an interface
  7. Specifying a more narrow type for the same property name
  8. Extend an interface as not Required in TypeScript
  9. Only set some of the properties of an interface to optional when extending
  10. Enforcing that a class satisfies one or more interfaces

# Extend an interface in TypeScript

Use the extends keyword to extend interfaces in TypeScript.

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 dog1: Dog = { name: 'Tom', age: 3, run() { console.log('the dog runs...'); }, };

extend interface in typescript

The code for this article is available on GitHub

The extends keyword removes the need of having to repeat the members of other types at multiple places.

It also shows the relation between the interfaces to the reader of the code.

# Extend multiple interfaces in TypeScript

You can also use the extends keyword to extend multiple interfaces.

index.ts
interface Person { name: string; } interface Employee { id: number; salary: number; } interface Developer extends Person, Employee { language: string; } const dev: Developer = { id: 1, name: 'Bobby Hadz', salary: 100, language: 'TypeScript', };

extend multiple interfaces in typescript

The main benefits of extending interfaces 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 two types.

The extends keyword removes the need of having to repeat the members of other types at multiple places.

You can extend from as many interfaces as necessary by separating the interfaces with a comma.

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 Person { name: string; } interface Employee { id: number; salary: number; } // πŸ‘‡οΈ Combine the Person and Employee interfaces interface Developer extends Person, Employee {} // πŸ‘‡οΈ Alternatively use an intersection type type Developer2 = Person & Employee; const dev: Developer = { id: 1, name: 'Bobby Hadz', salary: 100, };
The code for this article is available on GitHub

The example shows how you can combine two or more interfaces without adding new properties to them.

You can also use intersection types.

index.ts
interface Employee { id: number; name: string; } // πŸ‘‡οΈ Using intersection Types type Person2 = Employee & { country: string; }; const person2: Person2 = { id: 2, name: 'Bobby Hadz', country: 'Germany', };

You will most often see intersection types being used to combine existing types.

index.ts
interface A { a: string; } interface B { b: string; } // type Combined = { // a: string; // b: string; // } type Combined = A & B; const combined: Combined = { a: 'hello', b: 'world', };

You can use intersection types to combine as many types as necessary. The final type has all of the members from all of the specified types.

# Extending an interface with an existing Type Alias

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 animal1: Animal = { name: 'Tom', age: 3, guardian: true, };

extending interface with existing type alias

The code for this article is available on GitHub

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 that we are copying members from other more specific, named types and adding any new members we want to construct a final more generic interface.

I've also written a detailed guide on how to extend a type alias in TS.

# Extend an Interface excluding a Property in TypeScript

Use the Omit utility type to extend an interface excluding a property.

index.ts
interface Employee { id: number; name: string; salary: number; tasks: string[]; } // βœ… 1. Exclude 1 property // πŸ‘‡οΈ type WithoutTasks = { // id: number; // name: string; // salary: number; // } type WithoutTasks = Omit<Employee, 'tasks'>;

The Omit type can also be used to exclude one or more properties from a type alias, not just an interface.

index.ts
type Person = { name: string; age: number; country: string; }; // πŸ‘‡οΈ Type WithoutCountry = {name: string; age: number} type WithoutCountry = Omit<Person, 'country'>; const obj1: WithoutCountry = { name: 'bobby hadz', age: 30, }; // πŸ‘‡οΈ Type WithoutCountryAndAge = {name: string;} type WithoutCountryAndAge = Omit<Person, 'country' | 'age'>; const obj2: WithoutCountryAndAge = { name: 'bobby hadz', };

We used the Omit utility type to construct a new type based on the provided type with the specified keys removed.

index.ts
interface Employee { id: number; name: string; salary: number; tasks: string[]; } type WithoutTasks = Omit<Employee, 'tasks'>; const employee: WithoutTasks = { id: 1, name: 'Bobby Hadz', salary: 100, };

The first example creates a new type that has all of the properties in the Employee type excluding the tasks property.

# Overriding a property from an interface while extending it

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: 'Bobby Hadz', salary: 100, language: 'TypeScript', };

overriding property from interface while extending it

The code for this article is available on GitHub

The Omit utility type constructs a new type by picking the properties from the provided type and then it removes 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.

# Override the Type of multiple Interface properties

If you need to override the type of multiple interface properties, use a pipe | symbol to separate the types when passing them to the Omit utility type.

index.ts
interface Coords { address: string; x: number; y: number; } interface SpecificLocation extends Omit<Coords, 'address' | 'x' | 'y'> { address: { country: string; city: string; street: string; }; x: string; y: string; } const address: SpecificLocation = { x: '5', y: '10', address: { country: 'Chile', city: 'Santiago', street: 'Example street 123', }, };
The code for this article is available on GitHub

The first type we pass to the Omit utility type is the type from which we will omit one or more keys to construct a new type.

# Omit multiple properties before extending an 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: 'Bobby Hadz', salary: '30 K', language: 'TypeScript', };

omit multiple properties before extending interface

To pass multiple properties to 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.

# Specifying a more narrow type for the same property name

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: 'Bobby hadz', salary: 100, language: 'TypeScript', };
The code for this article is available on GitHub

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.

# Extend an interface as not Required in TypeScript

Use the Partial utility type to extend an interface as not required.

index.ts
interface Address { address: string; } interface SpecificLocation extends Partial<Address> { x: number; y: number; } const example: SpecificLocation = { x: 5, y: 10, };

The Partial utility type allows us to set all of the properties of a type to optional.

We passed the Location interface to the Partial utility type when extending it, so we are not required to set the address property when using the SpecificLocation type.

# Only set some of the properties of an interface to optional when extending

If you only need to set some of the properties of the interface to optional when extending it, you can:

  1. Use the Omit utility type to remove one or more properties from the interface.
  2. Combine Partial with the Pick utility type.
index.ts
interface Address { address: string; country: string; } interface SpecificLocation extends Omit<Address, 'address'>, Partial<Pick<Address, 'address'>> { x: number; y: number; } const example: SpecificLocation = { country: 'Chile', x: 5, y: 10, };
The code for this article is available on GitHub

The example looks a bit nasty but is not difficult to reason about.

The first thing we did was create a new interface based on the Address interface, for which the address property is omitted.

We used the Pick utility type to get the type of the address property from the Address interface and set it to optional with the Partial utility type.

In the SpecificLocation interface, the country property is required, but the address property is set to optional.

If you have to do this with multiple properties, you should separate them with a pipe | when passing them to the utility types.

index.ts
interface Address { address: string; country: string; city: string; } interface SpecificLocation extends Omit<Address, 'address' | 'city'>, Partial<Pick<Address, 'address' | 'city'>> { x: number; y: number; } const example: SpecificLocation = { country: 'Chile', x: 5, y: 10, };

In the example, we set the address and city properties to optional when extending the interface, however, the country property is still set to required.

This approach is much better than duplicating the same properties between multiple interfaces because we still signal to the reader of our code that there is a connection between the two interfaces.

If we just duplicated the properties and set them to not required, we would lose the connection between the interfaces.

# Enforcing that a class satisfies one or more interfaces

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'); } }

The class now specifies the required name and guardian properties and the run() method, so everything works as expected.

You can implement multiple interfaces by separating them with a comma.

index.ts
interface Dog { name: string; run(): void; } interface Shepherd { guardian: boolean; } class Animal implements Shepherd, Dog { name = 'Tom'; guardian = true; run() { console.log('The animal runs'); } }
The code for this article is available on GitHub

# Additional Resources

You can learn more about the related topics by checking out the following tutorials:

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.