All Articles

Typescript Interface vs Type Aliases

I have been working with typescript for almost a year now, and it has been one best things I have enjoyed learning and adopting as a React developer. It is quite a rich language and can be quite overwhelming especially if you coming from Javascript, but once you get to understand it you will realise there are some slightly confusing concepts. I struggled at first to wrap my head around the difference between interface and type aliases and when to use either one of them.

This article does a great job at describing in detail the similarities and differences between the two, please have a look at it. In this article I will just give a high level overview of how both of them work, which will assist in deciding which ones work best for you when you build your project. The difference between type alias and interfaces in TypeScript used to be more clear, but with the latest versions of TypeScript, they’re becoming more similar.

Objects / Functions

Both the interface and type alias define the shape of something. It is defining the contract by which a “thing” must abide by, but the syntax differs. Below i have presented the two methods one can use to define the structure of an object nad function arguments.

//**type alias**

/*object*/
type PersonType = {
	name: string;
  age: number;
}
const father: PersonType = {name: "Sizwe Ndlovu", age: 43}

/*function*/
type SetPersonDetails = (name: string, age: number) => void

**//interface**

/*object*/
interface PersonInterface {
	name: string;
  age: number;
}
const father: PersonInterface = {name: "Sizwe Ndlovu", age: 43}

/*function*/
interface SetPersonDetails {
	(name: string, age: number) => void;
}

Other Types

Unlike an interface, a type alias declaration can introduce a name for any kind of type, including primitive, union, tuples and intersection types. This is quite a very distinct and important difference between the two.

//**primitive**
type Name = string;

//**union**
/* Type alias for a union type */
type StringOrError = string | Error;

/* Type alias for union of many types */
type SeriesOfTypes = string | number | boolean | Error;

//intersection
interface Basketball {
  shoot: () => void;
}

interface Swimmer {
  stroke: () => void;
}

type BiAthlete = Swimmer & Basketball;

//**tuple**
let data: [number, string, boolean];
data = [24, "male ", false];
// Error: Type 'string' is not assignable to type 'boolean'
data = [54, "female", "blonde"];

Implements

A class can implement an interface or type alias to ensure it meets a particular contract. What classes cannot do is to implement / extend a type alias that names a union type because a class and interface are considered static blueprints.

//**interface**
interface Person {
    name: string
    age: number
}

class Manager implements Person {
    name = 'Mandla'
    age = 54
}

//**type**
type Person2 {
    name: string
    age: number
}

class Manager implements Person2 {
    name = 'Mandla'
    age = 54
}

//Error with union type
type Person3 = {name: string} | {age: number}
// A class can only implement an object type or intersection of object types with statically known members.
class Manager implements Person3 {
    name = 'Mandla'
    age = 54
}

Extends

Interfaces and type alias can both be extended. This method allows us to copy members of one (Interfaces and type alias) into another, which gives us flexibility into how we can separate Interfaces and type aliases into reusable components. An interface can extend a type alias and vice versa, the two are not mutually exclusive

//type alias extends type alias
type Person = { name: string };
type Developer = Person & { age: number };

//interface extends interface
interface Person {
  name: string;
}
interface Developer extends Person {
  age: number;
}

//interface extends type alias
type Person = { name: string };
interface Developer extends Person {
  age: number;
}

//type alias extends interface
interface Person {
  name: string;
}
type Developer = Person & { age: number };

Declaration merging

This is when the compiler merges two separate declarations declared with the same name into a single definition. This only applies to interfaces and not type alias

interface Person {
  age: number;
}
interface Person {
  name: string;
}

const person: Person = { age: 23, name: "Mqabuko" };

One may wonder, what could be the motivation for spreading the definition of your interface across different blocks. Below i share an example shared by Aluan Haddad that makes use of this merging behaviour.

//lib.d.ts
interface Array<T> {
  // map, filter, etc.
}
//array-flat-map-polyfill.ts
interface Array<T> {
  flatMap<R>(f: (x: T) => R[]): R[];
}

if (typeof Array.prototype.flatMap !== "function") {
  Array.prototype.flatMap = function(f) {
    return this.map(f).reduce((xs, ys) => [...xs, ...ys], []);
  };
}

Notice how no extends clause is present, although specified in separate files the interfaces are both in the global scope and are merged by name into a single logical interface declaration that has both sets of members. (the same can be done for module scoped declarations with slightly different syntax) By contrast, intersection types, as stored in a type declaration are closed not subject to merging.

Conclusion

Just because in many situations you can use either of them, it doesn’t mean you should use them interchangeably. Types have all the capabilities that interface has, expect for declaration merging, thus its vital you choose one for consistence throughout your project.

Please continue to check the Typescript documentation as new features keep getting added.