Last updated: Feb 29, 2024
Reading timeΒ·9 min
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.
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...'); }, };
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.
You can also use the extends
keyword to extend multiple interfaces.
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', };
The main benefits of extending interfaces are:
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.
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 example shows how you can combine two or more interfaces without adding new properties to them.
You can also use intersection types.
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.
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.
You can also use the extends
keyword to extend an interface with existing type
aliases.
type Dog = { name: string; age: number; }; interface Shepherd { guardian: boolean; } interface Animal extends Dog, Shepherd {} const animal1: 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.
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.
Use the Omit
utility type to extend an interface excluding a property.
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.
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.
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.
If you need to override a property from the interface you extend, you can use
the Omit
utility type.
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', };
The Omit utility type constructs a new type by picking the properties from the provided type and then it removes the specified keys.
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.
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; }
The best way to get around this is to remove the type when extending from the interface and override it in the new interface.
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.
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 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.
You can use a union of string literals to omit multiple properties from a type when extending.
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', };
To pass multiple properties to the Omit
utility type, separate the properties
with a pipe |
.
Note that when extending from an interface, you can provide a more narrow type for the same property name.
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 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
.
Omit
utility type to remove the specific property and override its type.Use the Partial
utility type to extend an interface as not required.
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.
If you only need to set some of the properties of the interface to optional when extending it, you can:
Partial
with the
Pick
utility type.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 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.
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.
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.
If we just duplicated the properties and set them to not required, we would lose the connection between the interfaces.
You can use the implements clause to enforce that a class satisfies one or more interfaces.
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.
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.
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'); } }
You can learn more about the related topics by checking out the following tutorials: