If you aren’t already familiar with the concept of interfaces or what they are in TypeScript, be sure to check out this article first, as Interface Declaration Merging is linked directly to them.

Otherwise, we shall proceed with the rest of the article.

What Does Interface Declaration Merging Refer To?

You might find yourself needing to update an already existing interface; however, that comes with the rest of breaking its usage elsewhere, so what do you do?

The best way to deal with that is through IDF or Interface Declaration Merging.

What Interface Declaration Merging allows us to do is that whenever we have two interfaces with the same name, the TypeScript compiler will try to merge the properties of both these interfaces into a single interface with the name they both share.

TypeScript allows merging multiple types such as interfaces, enums, namespaces, etc. One notable case where we cannot merge is classes. For that, we will need to use something called Mixins (Which will be covered in a future article here on Upmostly).

Examples of Interface Declaration Merging

We’ll try to cover all of the possible merge cases through code examples:

interface Car {
  model: string;
  engineSize: number;
}

interface Car {
  manufacturer: string;
}

interface Car {
  color: string;
}

const car: Car = {
  color: 'red',
  engineSize: 1968,
  manufacturer: 'BMW',
  model: 'M4',
  numSeats: 4, // `numSeats` property is not specified
};

Since all the declared interfaces share the same name, they will all be merged under that singular name while sharing the collective properties.

Also, all the properties with the same name shall be of the same type. Otherwise, there will be a compilation error thrown, as in the example below:

interface Car {
  model: string;
  engineSize: number;
}

interface Car {
  manufacturer: string;
}

interface Car {
  color: string;
  numSeats: string;
}

interface Car {
  numSeats: number; // Error: Subsequent property declarations must have the same type.  Property 'numSeats' must be of type 'string', but here has type 'number'.ts(2717)
}

const car: Car = {
  color: 'red',
  engineSize: 1968,
  manufacturer: 'BMW',
  model: 'M4',
  numSeats: 4,
};

Now we’ll look at interfaces with the same properties names whose types are functions:

interface Car {
  model: string;
  engineSize: number;
  manufacturer: string;
  color: string;
}

interface Car {
  start(param: string);
}

interface Car {
  start(param: number);
}

const bmw: Car = {
  model: 'M4',
  manufacturer: 'BMW',
  engineSize: 2993,
  color: 'white',
  start: (param) => param,
};

bmw.start(2) // `start(param: number)` will be used

bmw.start('3') // `start(param: string)` will be used

When interfaces containing functions with the same signature are merged, the functions in the last declared interfaces appear at the top of the merged interface, and the functions declared in the first interface appear beneath:

interface Car {
  start(param: string);
}

interface Car {
  start(param: any);
}

interface Car {
  start(param: number);
  start(param: boolean);
}

// This is how the final merged interface looks like
interface Car {
  // functions in the last interface appear at the top
  start(param: number);
  start(param: boolean);

  // function in the middle interface appears next
  start(param: any): number;

  // function in the first interface appears last
  start(param: string): string;
}

The reason for this is that interfaces that have been declared later have higher precedence over the ones that have been declared initially.

If we were to take into account our example from above, start(param: string) will never be called because in the final merged Car interface, the start(param: any): number method declaration comes before the start(param: string): string method declaration, and since any can stand for any type, it gets called even if a string is passed as an argument.

This is true for all interfaces except for the case where the same name function parameters have a string literal as a type. It follows the same order described above, but functions with string literal types are given higher precedence and, thus, through Declaration Merging, appear at the top.

As explained above, the string literals in later interfaces will appear before the initial interfaces.

Summary

I hope you’ve enjoyed the read and got a better understanding of what Interface Declaration Merging is, when it would be helpful to use, and, more importantly, how we would use it in our already existing Interfaces.

Also to note, interfaces are handy when working with TypeScript inside React; you can check out an article highlighting the creation of a new React project with TypeScript or even the addition of TypeScript to an already existing React project here.

👋 Hey, I'm Vlad Mihet
I'm Vlad Mihet, a blogger & Full-Stack Engineer who loves teaching others and helping small businesses develop and improve their technical solutions & digital presence.

💬 Leave a comment

Your email address will not be published. Required fields are marked *

We will never share your email with anyone else.