interface vs type — Choosing Well
Two ways to name an object shape, with real differences once you push on them.
For a plain object shape, interface and type are almost interchangeable, and either is fine. The differences show up at the edges, and knowing them lets you choose with intent rather than habit.
| Capability | interface | type |
|---|---|---|
| Object shapes | Yes | Yes |
| Unions / tuples / primitives | No | Yes |
| Declaration merging | Yes | No |
| extends / & composition | extends | & intersection |
| Mapped & conditional types | No | Yes |
interface Point { x: number; y: number }
type ID = string | number; // only type can do this
type Pair = [number, number]; // and thisA workable rule
Use interface for public object shapes and anything a class will implement or that a library might extend. Use type for unions, tuples, function types, and anything computed with mapped or conditional types.
Example
When to use it
- A library author uses interface for public API contracts because consumers can extend them via declaration merging.
- A developer uses a type alias for a complex union or tuple type that cannot be expressed with interface syntax.
- A team adopts interface for object shapes and type for computed or conditional types to keep conventions consistent.
More examples
Declaration merging with interface
Two interface declarations with the same name merge their members — a feature unique to interfaces, unavailable with type.
interface Window {
myPlugin: { version: string };
}
// Two declarations of the same interface merge automatically
interface Window {
otherProp: number;
}
// Window now has both myPlugin and otherPropUnion type — only with type
Union types must be declared with type; interfaces cannot represent unions, making type the required choice here.
// Only type aliases support union types
type ID = string | number;
type NullableString = string | null;
// interface ID = string | number; // syntax errorExtending — interface and type
Both interface extends and type intersection compose types, but the syntax and some edge-case semantics differ.
interface Named { name: string; }
interface Aged extends Named { age: number; } // interface extends
type Tagged = Named & { tag: string }; // type intersection
const a: Aged = { name: 'Alice', age: 30 };
const b: Tagged = { name: 'Bob', tag: 'admin' };
Discussion