Back to stories
<Frontend/>

TypeScript for Frontend (Part 1): interface vs type

Share by

TypeScript for Frontend (Part 1): interface vs type

In TypeScript you can describe object shapes with either interface or type. They overlap a lot, but they're not the same. This post walks through the real differences and when to use each.


The basics

Both can describe an object:

// interface
interface User {
  id: string
  name: string
}

// type alias
type User = {
  id: string
  name: string
}

For plain object shapes, either works. The differences show up when you extend, merge, or need unions and primitives.


Declaration merging (interface only)

Interfaces can be declared multiple times; TypeScript merges them into a single type.

interface Window {
  title: string
}

interface Window {
  width: number
}

// Result: Window has both title and width
const w: Window = { title: 'App', width: 800 }

This is how global augmentation (e.g. adding properties to Window) is typically done.

Types cannot be merged. A second type Window = ... would be a redeclaration error.

So: if you need declaration merging (e.g. for ambient declarations or plugins), use interface.


Extending vs intersecting

Interfaces use extends:

interface Animal {
  name: string
}

interface Dog extends Animal {
  breed: string
}

Types use intersection (&):

type Animal = {
  name: string
}

type Dog = Animal & {
  breed: string
}

For object shapes, both styles are equivalent in practice. Prefer extends when you're building a hierarchy of named interfaces; use & when you're composing types (including unions and other type expressions).


Unions and primitives (type only)

Type aliases can represent unions, primitives, and tuples. Interfaces cannot.

// Union of string literals
type Status = 'pending' | 'success' | 'error'

// Union of types
type Result = { ok: true; data: unknown } | { ok: false; error: string }

// Primitives
type ID = string
type Count = number

// Tuple
type Pair = [string, number]

So: for unions, aliases to primitives, or tuples, you must use type.


Mapped types and conditional types

Advanced type-level logic is done with type aliases. Interfaces can't express mapped or conditional types.

// Mapped type: make all properties optional
type PartialUser = {
  [K in keyof User]?: User[K]
}

// Conditional type
type NonNullable<T> = T extends null | undefined ? never : T

If you're building utility types or transforming existing types, use type.


Implementing in a class

A class can implement either an interface or a type that describes an object. You can't implements a union type.


When to use which

  • Use interface when: You're defining an object shape and may want declaration merging; or you're building a hierarchy of named object types with extends.
  • Use type when: You need unions, primitives, tuples, or mapped/conditional types.
  • Either is fine when: You only need a single object shape, no merging, no unions. Prefer interface if you like extends and named display; type if you prefer consistency with the rest of your type aliases.

Summary

Featureinterfacetype
Object shapeYesYes
Declaration mergingYesNo
Extends / intersectionYesYes
UnionsNoYes
Primitives / tuplesNoYes
Mapped / conditionalNoYes
Class implementsYesYes (object type)

interface: best for named object contracts and when you need merging. type: required for unions, primitives, tuples, and advanced type logic.