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.

Interfaces vs Types in Typescript
Interfaces vs Types in Typescript

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.

Property 'legs' is required in type 'Humans'
Property 'legs' is required in type 'Humans'

See Typescript playground.

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 
}  
Duplicate identifier 'Human' error
Duplicate identifier 'Human' error

See Typescript playground.

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 
}  

See Typescript playground

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
}
A class can only implement an object type or intersection of object types with statically known members.
A class can only implement an object type or intersection of object types with statically known members.

See Typescript playground.

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.

Get a Free Typescript Book?

image-148
Build strongly typed Polymorphic React components 

Get this book for free