DEV Community

Cover image for Interface Extensions/Merging in TypeScript
Tomohiro Yoshida
Tomohiro Yoshida

Posted on

Interface Extensions/Merging in TypeScript

TypeScript has the capability of extending and merging interfaces.

Our development will be more efficient by utilizing these advanced features.

Basic Usage of Interfaces

Interfaces are similar to object-type aliases. Here is a comparison between an interface and an aliased object type.

type Vehicle = {
    wheels: number;
    maker: string;
};

interface Vehicle {
    wheels: number;
    maker: string;
}
Enter fullscreen mode Exit fullscreen mode

This type and interface can be used in almost the same way.

In this article, I will dig deeper into the interface to introduce more advanced techniques.

Interface Extensions

You may notice that some interfaces sometimes look similar to each other when it comes to a big project. If some interfaces have common properties, it is a good time to extend one interface and make another one. Let me show an example:

interface Vehicle {
    wheels: number;
    maker?: string;
}

interface Car extends Vehicle {
    power: "gas" | "electricity";
}

interface Bicycle extends Vehicle {
    folding: boolean;
}

const myCar: Car = {
    wheels: 4,
    maker: "Toyota",
    power: "gas"
} // OK

const car: Car = {
    wheels: 4,
    maker: "Honda",
    power: "gas",
    folding: false
} // Error
// Type '{ wheels: number; maker: string; power: "gas"; folding: boolean; }' is not assignable to type 'Car'.
    // Object literal may only specify known properties, and 'folding' does not exist in type 'Car'.

const bicycle: Bicycle = {
    wheels: 2,
} // Error
// Property 'folding' is missing in type '{ wheels: number; }' but required in type 'Bicycle'.
Enter fullscreen mode Exit fullscreen mode

Both Car and Bicycle interfaces have common properties, wheels (required) and a maker (optional). Therefore, they can derive these properties from the Vehicle interface.

Cool! You can avoid repetitive code by using this technique.

What If You Declare the Same Property in a Child Interface?

It is possible to declare the same name of the property again when making a descendant interface. However, it does not mean you can freely override the parent’s property. The rule is you can override a property as long as a new re-declared property is assignable to the parent one.

Here is an example:

interface Vehicle {
    wheels: number;
    maker?: string;
}

interface Car extends Vehicle {
    power: "gas" | "electricity";
}

interface ElectricCar extends Car {
    power: "electricity";
} // OK

interface FuturisticCar extends Car {
    power: "air";
} // Error
// Interface 'FuturisticCar' incorrectly extends interface 'Car'.
  // Types of property 'power' are incompatible.
    // Type '"air"' is not assignable to type '"gas" | "electricity"'.
Enter fullscreen mode Exit fullscreen mode

As you can see above, "electricity" is assignable to the literal union type, "gas" | "electricity", because "electricity" is included in the union type. Thus, it’s possible to make ElectricCar by extending the Car interface.

On the other hand, the TypeScript type checker yells at you when making the FuturisticCar interface because "air" is not included in "gas" | "electricity”.

Interface Merging

In the previous section, I explained what happens when you declare the same property while extending another interface.

Next, guess what happens when you declare multiple interfaces with the same name.

interface Vehicle {
    wheels: number;
    maker?: string;
}

interface Vehicle {
    price: number;
}

const vehicle: Vehicle = {
    wheels: 4,
    maker: "Nissan",
    price: 25000
} // OK

const vehicle2: Vehicle = {
    maker: "Nissan",
    price: 25000
} // Error
// Property 'wheels' is missing in type '{ maker: string; price: number; }' but required in type 'Vehicle'.

const vehicle3: Vehicle = {
    wheels: 4,
    maker: "Nissan",
} // Error
// Property 'price' is missing in type '{ wheels: number; maker: string; }' but required in type 'Vehicle'.
Enter fullscreen mode Exit fullscreen mode

In this example, Vehicle is declared twice. Since both interfaces have the same name, TypeScript merges them into one interface. As a result, the final Vehicle interface has wheels, maker, and price properties. Technically, it is the same as declaring one Vehicle interface with the three properties.

But what if those interfaces have the same property name with different types?

Let’s take a look at the example code below:

interface Vehicle {
    wheels: number;
    maker?: string;
    plate: number;
}

interface Vehicle {
    plate: string;
} // Error
// Subsequent property declarations must have the same type.  Property 'plate' must be of type 'number', but here has type 'string'.
Enter fullscreen mode Exit fullscreen mode

It is not allowed to declare the same property with different types because it causes conflicts. The TypeScript type checker will warn that subsequent property declarations must have the same type.

Conclusion

  • Interface extensions allow developers to avoid repetitive code by deriving common properties from a parent interface.
  • When declaring the same property in a child interface, it is only allowed if the new re-declared property is assignable to the parent one.
  • Interface merging can be used to combine interfaces with the same name, but it is not allowed to declare the same property with different types as it causes conflicts.

Top comments (0)