Interfaces vs Types in Typescript
When beginning Typescript, you may find it confusing to settle on a choice. This article clears up the confusion and helps you choose which is right for you.
The interfaces vs types (technically, type alias) conversation is a well contested one.
When beginning Typescript, you may find it confusing to settle on a choice. This article clears up the confusion and helps you choose which is right for you.
TLDR
In many cases, you can use either an interface or type alias exchangeably.
Almost all features of an interface are available via type aliases except you cannot add new properties to a type by re-declaring it. You must use an intersection type.
Why the Confusion in the first place
Whenever we’re faced with multiple options, most people begin to suffer from the paradox of choice.
In this case, there are just two options.
What’s so confusing about this?
Well, the main confusion here stems from the fact that these two options are so evenly matched in most regards.
This makes it difficult to make an obvious choice — especially if you’re just starting out with Typescript.
A Basic Example
Let’s get on the same page with quick examples of an interface and a type alias.
Consider the representations of a Human
type below:
// type
type Human = {
name: string
legs: number
head: number
}
// interface
interface Human {
name: string
legs: number
head: number
}
These are both correct ways to denote the Human
type i.e., via a type alias or an interface.
The differences between Type aliases and Interfaces
Below are the main differences between a type alias and an interface:
Key difference: interfaces can only describe object shapes. Type aliases can be used for other types such as primitives, unions and tuples
A type alias is quite flexible in the data types you can represent. From basic primitives to complex unions and tuples as shown below:
// primitives
type Name = string
// object
type Male = {
name: string
}
type Female = {
name: string
}
// union
type HumanSex = Male | Female
// tuple
type Children = [Female, Male, Female]
Unlike, type aliases, you may only represent object types with an interface.
Key difference: interfaces can be extended by declaring it multiple times
Consider the following example:
interface Human {
name: string
}
interface Human {
legs: number
}
The two declarations above will become:
interface Human {
name: string
legs: number
}
Human
will be treated as a single interface: a combination of the members of both declarations.
This is not the case with type aliases.
With a type alias, the following will lead to an error:
type Human = {
name: string
}
type Human = {
legs: number
}
const h: Human = {
name: 'gg',
legs: 5
}
With Type aliases, you’ll have to resort to an intersection type:
type HumanWithName = {
name: string
}
type HumanWithLegs = {
legs: number
}
type Human = HumanWithName & HumanWithLegs
const h: Human = {
name: 'gg',
legs: 5
}
Minor difference: Both can be extended but with different syntaxes
With interfaces, you use the extends
keyword. For types, you must use an intersection.
Consider the following examples:
Type alias extends type alias
type HumanWithName = {
name: string
}
type Human = HumanWithName & {
legs: number
eyes: number
}
Type alias extends an interface
interface HumanWithName {
name: string
}
type Human = HumanWithName & {
legs: number
eyes: number
}
Interface extends interface
interface HumanWithName {
name: string
}
interface Human extends HumanWithName {
legs: number
eyes: number
}
An interface extends a type alias
type HumanWithName = {
name: string
}
interface Human extends HumanWithName {
legs: number
eyes: number
}
As you can see, this is not particularly a reason to choose one over the other. However, the syntaxes differ.
Minor difference: classes can only implement statically known members
A class can implement both interfaces or type aliases. However, a class cannot implement or extend a union type.
Consider the following example:
Class implements interface
interface Human {
name: string
legs: number
eyes: number
}
class FourLeggedHuman implements Human {
name = 'Krizuga'
legs = 4
eyes = 2
}
Class implements type alias
type Human = {
name: string
legs: number
eyes: number
}
class FourLeggedHuman implements Human {
name = 'Krizuga'
legs = 4
eyes = 2
}
These both work without any errors. However, the following fails:
Class implements union type
type Human = {
name: string
} | {
legs: number
eyes: number
}
class FourLeggedHuman implements Human {
name = 'Krizuga'
legs = 4
eyes = 2
}
Summary & Personal preference
Your mileage may differ but wherever possible, I stick to type aliases for their flexibility and simpler syntax i.e., I choose type aliases except I specifically need features from an interface.
For the most part, you can also choose based on your personal preference, but stay consistent with your choice — at least in a single given project.
For completeness, I must add that. In performance-critical types, interface comparison checks can be faster than type aliases. I’m yet to find this to be an issue.