Tutorial

Interface Declaration Merging in TypeScript

Published on August 23, 2019
Default avatar

By Alfred M. Adjei

Interface Declaration Merging in TypeScript

While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking 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. 😊😎

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Alfred M. Adjei

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel