When to use Type Aliases or Interfaces in TypeScript
Type aliases and interfaces in TypeScript have similar capabilities. In this post, 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.
Did you find this post useful?
Let me know by sharing it on Twitter.If you to learn more about TypeScript, you may find my free TypeScript course useful: