Learn TypeScript

Interfaces v type aliases

Interfaces v type aliases

In previous lessons, we have learned that type aliases and interfaces have similar capabilities. In this lesson, we discuss which approach is best for different use cases.

Representing primitive types

Type aliases can represent primitive types, but interfaces can't.

type Name = string;

Winner: Type alias

It is worth noting that it is generally simpler to use the primitive type directly rather than aliasing it.

Representing arrays

Type aliases and interfaces can both represent arrays.

Let's compare the syntax for both approaches:

type Names = string[];
interface Names {
[index: number]: string;
}

The type alias approach is a lot more concise and clearer.

Winner: Type alias

It is worth noting that it is often simpler to use the array type directly rather than aliasing it.

Representing tuples

Winner: Type aliases can represent tuple types, but interfaces can't:

type Point = [number, number];

Winner: Type alias

Representing functions

Type aliases and interfaces can both represent functions.

Let's compare the syntax for both approaches:

type Log = (message: string) => void;
interface Log {
(message: string): void;
}

The type alias approach is a lot more concise and clearer.

Winner: Type alias

Creating union types

Type aliases can represent union types but interfaces can't:

type Status = "pending" | "working" | "complete";

Winner: Type alias

Representing objects

Type aliases have wiped the floor with interfaces so far. However, the strength of interfaces is representing objects.

Let's compare the syntax of both approaches:

type Person = {
name: string;
score: number;
};
interface Person {
name: string;
score: number;
}

The type alias approach is again a little more concise, but the equals operator (=) can result in the statement being confused for a variable assignment to an object literal.

Winner: Tie

Composing objects

Type aliases and interfaces can both compose objects together.

Let's compare the syntax of both approaches:

type Name = {
firstName: string;
lastName: string;
};
type PhoneNumber = {
landline: string;
mobile: string;
};
type Contact = Name & PhoneNumber;
interface Name {
firstName: string;
lastName: string;
}
interface PhoneNumber {
landline: string;
mobile: string;
}
interface Contact extends Name, PhoneNumber {}

The type alias approach is more concise.

Type aliases can compose interfaces and visa versa:

type Name = {
firstName: string;
lastName: string;
};
interface PhoneNumber {
landline: string;
mobile: string;
}
type Contact = Name & PhoneNumber;

Only type aliases can compose union types though:

type StringActions = { type: "loading" } | { type: "loaded"; data: string[] };
type NumberActions = { type: "loading" } | { type: "loaded"; data: number[] };
type Actions = StringActions & NumberActions;

Winner: Type alias

Authoring a library

One important feature that interfaces have that type aliases don't is declaration merging:

interface ButtonProps {
text: string;
onClick: () => void;
}
interface ButtonProps {
id: string;
}

This is is useful for adding missing type information on 3rd party libraries. If you are authoring a library and want to allow this capability, then interfaces are the only way to go.

Winner: Interfaces

Summary

Type aliases generally have more capability and a more concise syntax than interfaces. However, interfaces have a nice syntax for objects, and you might be used to this concept from other languages.

The important thing is to be consistent in whichever approach is used so that the code isn't confusing.

Note that this course will deliberately use a mixture of both approaches so that we become comfortable with both of them.

Now that we have used and created a wide range of types, we will learn to think about types as sets of values in the next lesson.

© 2024 Carl Rippon
Privacy Policy
This site uses cookies. Click here to find out more