We can extend the Interfaces in TypeScript to include other interfaces. This helps us to create a new interface consisting of definitions from the other interfaces. Extending interfaces gives us more flexibility in how we define our interfaces and reuse the existing interfaces.
Table of Contents
Extending Interface
We extend an interface by using the extends keyword after the interface and name followed by a list of interfaces each separated by a comma.
This example Employee interface extends the Address interface. The employee object must contain all the properties from both the interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | interface Address { address: string; city:string state:string } interface Employee extends Address { firstName: string; lastName: string; fullName(): string; } let employee: Employee = { firstName : "Emil", lastName: "Andersson", fullName(): string { return this.firstName + " " + this.lastName; }, address:"India", city:"Mumbai", state:"Maharastra", } |
You can extend an interface from several other interfaces.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | interface Address { address: string; city:string state:string } interface Employment { designation: string; } interface Employee extends Address, Employment { firstName: string; lastName: string; fullName(): string; } let employee: Employee = { firstName : "Emil", lastName: "Andersson", fullName(): string { return this.firstName + " " + this.lastName; }, address:"India", city:"Mumbai", state:"Maharastra", designation:"CEO" } |
In this example, both Customer and Employee Interface extends the Person interface which in turn extends an Address interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | interface Address { address: string; city:string state:string } interface Person extends Address { firstName: string; lastName: string; fullName(): string; } interface Customer extends Person { customeId:number } interface Employee extends Person { employeeId:number } let c:Customer= { customeId:100, firstName:"", lastName:"", fullName: function() {return this.firstName+" "+ this.lastName}, address:"", city:"", state:"" } let e:Employee= { employeeId:100, firstName:"", lastName:"", fullName: function() {return this.firstName+" "+ this.lastName}, address:"", city:"", state:"" } |
Common Properties are merged
If the Interface and the extended interfaces contain a common property, then their data type must be compatible with each other. In such cases they are merged else the compiler throws an error.
The BetterProduct extends the Product interface. Both the Interfaces contain a common property id. Since the data type matches, the compiler merges both together and does not complain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | interface Product { id: number; name:string } interface BetterProduct extends Product { id:number; price:number } let product:BetterProduct = { id:1, name:"Mobile", price:100 } |
But if you change the data type (id is a string in BetterProduct interface) as in this example, the compiler throws the error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | interface Product { id: number; name:string } interface BetterProduct extends Product { id:string; price:number } let product:BetterProduct = { id:1, name:"Mobile", price:100 } //Compiler error //Interface 'BetterProduct' incorrectly extends interface 'Product'. // Types of property 'id' are incompatible. // Type 'string' is not assignable to type 'number'. |
Note that data types need not be the same. But they must be compatible with each other. In the example below the id property has number and any data types. Since they are compatible with each other compiler does not complain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | interface Product { id: number; name:string } interface BetterProduct extends Product { id:any; price:number } let product:BetterProduct = { id:1, name:"Mobile", price:100 } |
This example has id property with unknown type & number data type. The number can be assigned to an unknown data type. Since the BetterProduct extends id from unknown to a number this code is acceptable.
1 2 3 4 5 6 7 8 9 10 11 | interface Product { id: unknown; name:string } interface BetterProduct extends Product { id:number; price:number } |
But unknown cannot be assigned to a number data type. Hence BetterProduct cannot extend the number to an unknown type. This code throws an error.
1 2 3 4 5 6 7 8 9 10 11 | interface Product { id: number; name:string } interface BetterProduct extends Product { id:unknown; price:number } |
Common functions are merged if the signature is Compatible
Similar to the properties, the functions with the same name are merged only if they have compatible signatures. If the function signatures are not compatible then they are not merged and the compiler will throw an error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | interface IOne { prop1:string, someFn():string } interface ITwo { address: string; someFn():number } interface IThree extends IOne, ITwo { } //Interface 'IThree' cannot simultaneously extend types 'IOne' and 'ITwo'. // Named property 'someFn' of types 'IOne' and 'ITwo' are not identical |
Readonly Properties
The extended interfaces can override the readonly and optional properties.
This example defines a read-only name property in the Person Interface. The Customer interface extends it and also declares the name property without the readonly modifier. This allows us the modify the name property on any object which is created from the Customer Interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | interface Person { readonly name: string; } interface Customer extends Person { name: string; //overrides the name from the person. customeId:number } let customer:Customer= { customeId:100, name:"Luis T. Mackey" } customer.name="Luis T. Mackey"; //Ok interface Employee extends Person { employeeId:number } let employee:Employee= { employeeId:100, name:"" } employee.name="Luis T. Mackey"; //Cannot assign to 'name' because it is a read-only property. |
The employee interface does not override the name property. Typescript compiler throws an error if any object created from the Employee Interface tries to modify the name property.
Interfaces Extending Classes
An Interface can also extend classes. The extended interface will include all class members (both public and private) but without any implementation.
In this example, the Employee extends the Person class and adds a new property Designation. The newly created employee must contain the name property which is from the Person class along with the Designation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Person { public name:string; constructor(name:string) { this.name=name; } } interface Employee extends Person { Designation: string; } let employee:Employee= { name:"Jason B. McAtee", Designation:"Manager", } |
If the class contains a function, its implementation is not copied. The object created using the interface must provide its own implementation. The employee object in the following example must implement the printName function from the Person class although the class contains its implementation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | class Person { public name:string; constructor(name:string) { this.name=name; } printName() { console.log(this.name) } } interface Employee extends Person { Designation: string; } let employee:Employee= { name:"Jason B. McAtee", Designation:"Manager", printName() { console.log(this.name) } } |
Extending the class has one significant restriction. If the class from which we extend has private property (or protected property), then we can use the interface only with those classes which extend the class that the interface has extended.
For Example, we have added a private property (id) to the Person class. The IEmployee interface extends the Person class. But when we create a new object employee from the interface IEmployee the compiler will throw the error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class Person { private id:number; public name:string; constructor(name:string) { this.id=0; this.name=name; } } interface IEmployee extends Person { Designation: string; } let employee:IEmployee= { name:"Jason B. McAtee", Designation:"Manager", } //Property 'id' is missing in type '{ name: string; Designation: string; }' but required in type 'Employee'. //'id' is declared but its value is never read. |
If we do not add the id property to the employee object, the compiler complains that the Property 'id' is missing in type' employee. But if we add the id property, it still complains because the id is a private property adding it to the employee object makes it public property.
Hence, the only way you can use it is to create a new class that extends the class (or subclasses of that class) from which the interface extends.
For Example, the IEmployee interface extends the class Person, which has private property. We can use the interface only on those classes which extend from the IEmployeePerson class (or any subclass of Person class).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class Person { private id:number; public name:string; constructor(name:string) { this.id=1; this.name=name; } } interface IEmployee extends Person { Designation: string; } class Employee extends Person implements IEmployee { Designation:string constructor(name:string , designation:string) { super(name) this.Designation=designation; } } let employee = new Employee("Jason B. McAtee","Manager") |
Extending Interface Vs Merging Interface
Declaration Merging is another way by which you can extend an interface. This you can do by declaring the same interface again with new properties.
For Example, the following code is perfectly valid and the compiler does not throw any errors. First, we create Person interface with only two properties. Later we add address property to the Person.
1 2 3 4 5 6 7 8 9 10 11 12 13 | interface Person { firstName: string; lastName: string; } //... //... interface Person { address:string } |
The typescript behind the scene merges both interfaces into one. This is known as Declaration Merging.
1 2 3 4 5 6 7 | interface Person { firstName: string; lastName: string; address:string } |
In interface merging the common properties with the same data type are merged. The common functions with the same function signatures are also merged.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | interface Product { id: number; name:string } interface Product { id:number; price:number } let product:Product = { id:1, name:"Mobile", price:100 } |
But the common functions with different signatures are overloaded. In this example, Product interface declares the someFn with different signatures. In the first interface, it takes a string as the argument and returns the string. While in the second interface it takes the number as the argument and returns the number.
Typescript merges both of them and creates an overload signature of the function. You can read more about function overloading.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | interface Product { id: number; name:string someFn(para:string) : string } interface Product { id:number; price:number someFn(para:number) : number } let product:Product = { id:1, name:"Mobile", price:100, someFn: function(para:string|number):any { return para; } } |


