Objects and unions

TypeScript interfaces vs types: which one to use

TypeScript interfaces vs type aliases: practical differences, unions, declaration merging, and which one to pick for a consistent codebase.

Pick one and stay consistent. I use type aliases for almost everything. They cover object shapes, unions, and primitives in one syntax, and I never have to switch keywords.

Both type and interface can describe an object. You give a name to a shape, then use that name on variables and function parameters. For plain objects, the two are interchangeable.

Describing objects

With a type alias:

type Dog = {
  name: string
  age: number
}

With an interface:

interface Dog {
  name: string
  age: number
}

Notice the small syntax difference. The alias has an = sign, because you are assigning a shape to a name. The interface has none.

You use them the same way:

const jack: Dog = {
  name: 'Jack',
  age: 3
}

Optional properties work with both. Add ? after the property name:

type Dog = {
  name: string
  age?: number
}

Interfaces can extend other interfaces, which copies the parent’s properties into the child:

interface Animal {
  name: string
}

interface Dog extends Animal {
  age: number
}

Type aliases get the same result with an intersection, the & operator:

type Animal = {
  name: string
}

type Dog = Animal & {
  age: number
}

Either way, a Dog needs both name and age.

What only types can do

Type aliases can name primitives, unions, and intersections. Interfaces cannot. An interface always describes an object shape.

type ID = string | number

type Status = 'idle' | 'loading' | 'done'

type Point = { x: number } & { y: number }

This is why I reach for type first. One keyword covers object shapes and all of these other patterns. With interfaces, the moment I need a union I have to switch to type anyway.

What only interfaces can do

Interfaces support declaration merging. Declare the same interface twice and TypeScript merges the two into one:

interface User {
  name: string
}

interface User {
  age: number
}

const jack: User = {
  name: 'Jack',
  age: 3
}

Do the same with type User twice and you get a duplicate identifier error.

Merging is useful when a library needs to add properties to a global type across several files, like extending Window or Express’s Request. For your own app code, it is mostly a footgun. A property can appear from a file you did not open, and nothing in the first declaration hints that a second one exists.

My verdict

For day-to-day app code, pick type or interface and stick with it. I prefer type because it handles unions and intersections without switching syntax, and most lessons in this course use it.

If you work on a library that augments global types, interfaces have a clear edge. Otherwise the choice matters far less than consistency across your team.

New to the language? Start with our TypeScript hub. Once you know the basics, make sure your tsconfig.json has strict enabled.

Lesson completed