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
interfacewhen: You're defining an object shape and may want declaration merging; or you're building a hierarchy of named object types withextends. - Use
typewhen: 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
interfaceif you likeextendsand named display;typeif you prefer consistency with the rest of your type aliases.
Summary
| Feature | interface | type |
|---|---|---|
| Object shape | Yes | Yes |
| Declaration merging | Yes | No |
| Extends / intersection | Yes | Yes |
| Unions | No | Yes |
| Primitives / tuples | No | Yes |
| Mapped / conditional | No | Yes |
| Class implements | Yes | Yes (object type) |
interface: best for named object contracts and when you need merging. type: required for unions, primitives, tuples, and advanced type logic.