TypeScript Type Aliases vs Interfaces

Mar 08, 2022typescript

In TypeScript, type aliases and interfaces are very similar, and in many cases you can choose between them freely. However, and as soon as you need to compose two or more types, you have the option of extending those types with an interface, or intersecting them in a type alias, and that’s when the differences start to matter.

Use interface until you need to use features from type alias.

Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

Type aliases

A type alias is a name for any type, you can actually use a type alias to give a name to any type at all, not just an object type.

// primitive
type SanitizedString = string

// union
type ID = number | string

// tuple
type Data = [number, string]

// function
type SetPoint = (x: number, y: number) => void

// object
type Foo = {
  x: number
  y?: boolean
  handler: () => void
}

// index signature
type NumbersNames = {
  [key: string]: string
}

Interfaces

An interface may only be used to declare the shapes of objects, not rename primitives. Being concerned only with the structure and capabilities of types is why we call TypeScript a structurally typed type system.

// object
interface Bar {
  x: number
  y? boolean
  print(text: string): void
}

// function
interface SetPoint {
  (x: number, y: number): void
}

// index signature
interface NumbersNames {
  [key: string]: string
}

Structural type system

Both can used to declare shape of object. Because TypeScript is a structural type system, it’s possible to intermix their use too.

type BirdType = {
  wings: 2
}

interface BirdInterface {
  wings: 2
}

const bird1: BirdType = {wings: 2}
const bird2: BirdInterface = {wings: 2}
const bird3: BirdInterface = bird1

Declaration merging (interface only)

One major difference between type aliases vs interfaces are that interfaces are open and type aliases are closed. This means you can extend an interface by declaring it multiple times, and will be treated as a single interface (with members of all declarations being merged).

interface Point {
  x: number
}
interface Point {
  y: number
}
const point: Point = {x: 1, y: 2}

In the other case a type cannot be changed outside of its declaration.

type Puppy = {
  color: string
}

type Puppy = {
  toys: number
}

// Error: duplicate identifier Puppy

Depending on your goals, this difference could be a positive or a negative. However for publicly exposed types, it’s a better call to make them an interface.

Always use interfaces for public API’s definition when authoring a library or 3rd party ambient type definitions.

Both can be extended

They both support extending other interfaces and types. Type aliases do this via intersection types, while interfaces have a keyword.

type Owl = {nocturnal: true} & BirdType
type Robin = {nocturnal: false} & BirdInterface

interface Peacock extends BirdType {
  colorful: true
  flies: false
}
interface Chicken extends BirdInterface {
  colorful: false
  flies: false
}

let owl: Owl = {wings: 2, nocturnal: true}
let chicken: Chicken = {wings: 2, colorful: false, flies: false}

Both can be implemented

A class can implement an interface or type alias, both in the same exact way. Note however that a class and interface are considered static blueprints. Therefore, they can not implement / extend a type alias that names a union type.

// interface
interface Point {}
class SomePoint implements Point {}

// type alias
type Point = {}
class SomePoint implements Point {}

// Error: can not implement a union type
type PartialPoint = {x: number} | {y: number}
class SomePartialPoint implements PartialPoint {}