While this tutorial has content that we believe is of great benefit to our community, we have not yet tested or edited it to ensure you have an error-free learning experience. It's on our list, and we're working on it! You can help us out by using the "report an issue" button at the bottom of the tutorial.

In our previous post on TypeScript Mixins, we talked briefly about declaration merging in TypeScript. In these series of posts, we are going to go a little deeper starting with interfaces with interface declaration merging.

What is Declaration Merging?

Declaration merging is when the TypeScript complier merges two or more types into one declaration provided they have the same name.

TypeScript allows merging between multiple types such as interface with interface , enum with enum , namespace with namespace , etc. One notable merge that isn’t permitted is class with class merging. If you want a functionality like that, checkout mixins.

Let’s get started with interface with interface merging by looking at an example:

interface Person { name: string; } interface Person { age: number; } interface Person { height: number; } class Employee implements Person { name = "Mensah" age = 100; height = 40 } const employee = new Employee(); console.log(employee) // {name: "Mensah", age: 100, height: 40}

Since all the interfaces were declared with the same name, Person , they are merged into one definition so the Employee class contains the properties from all the interfaces.

Same property names in interfaces (non-functions)

If any of the interfaces to be merged contain the same property name and that property isn’t a function, then the type of the properties must be the same or else the complier will throw an error.

interface Person { name: string; zipCode: string; } interface Person { age: number; zipCode: string; // acceptable } interface Person { zipCode: number; // error }

Same property names in interfaces (functions)

When the elements in the merged interfaces are functions and they have the same name, they are overloaded, that is, depending on the type of argument passed, the appropriate function will be called.

interface Person { speak(words: string); } interface Person { speak(words: number); } const person: Person = { speak: (wordsOrNum) => wordsOrNum } console.log(person.speak("Hi")) // speak(words: string) is used console.log(person.speak(2)) // speak(words: number) is used

When interfaces containing same-signature functions 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 Person { speak(words:string); } interface Person { speak(words: any); } interface Person { speak(words: number); speak(words: boolean); } // This is how the final merged interface looks like interface Person { // functions in the last interface appear at the top speak(words: number); speak(words: boolean); // function in the middle interface appears next speak(words: any):number; // function in the first interface appears last speak(words: string):string; }

The reason for this is, is that later interfaces declared have a higher precedence over the initial interfaces declared. So in our example above, speak(words: string) will never be called because in the final merged Person interface, speak(words: any):number comes before speak(words: string):string and since any can stand for any type, it gets called even if a string is passed as an argument .

To prove this, when you hover over the per variable in the code below, it will display const per: number and not const per: string even though we are passing in a string argument.

const per = person.speak("bacon");

This is true for all interfaces expect when the same name functions parameters have a string literal as a type . It follows the same order described above but functions with string literal types are given a higher precedence and therefore appear at the top.

It’s worth noting that, the string literals in later interfaces will appear before the initial interfaces like explained above.

interface Person { speak(words: number); speak(words: "World!"); // string literal type } interface Person { speak(words: "Hello"); // string literal type } interface Person { speak(words: string); } // merged interface output. interface Person { // string literals are given precedence speak(words: "Hello"); speak(words: "World!"); // the rest of the functions are arranged using the same rules. speak(words: string); speak(words: number); }

That’s it. Hope this was useful. 😊😎