Interfaces vs Type Aliases
When to use interface vs type in TypeScript — extension, unions, declaration merging, and a practical default for frontend apps.
- typescript
- interfaces-vs
Both interface and type can describe object shapes. Interviews love “which one?” The honest answer: mostly interchangeable for object types; differences matter for unions, tuples, mapped types, and declaration merging.
Docs: Object Types, Everyday Types.
Same job, two spellings
interface UserI {
id: string;
name: string;
}
type UserT = {
id: string;
name: string;
};
const u1: UserI = { id: '1', name: 'Ada' };
const u2: UserT = { id: '1', name: 'Ada' };
For props, DTOs, and domain models, either works if the team is consistent.
What only type does well
Unions and intersections as top-level aliases:
type Status = 'idle' | 'loading' | 'done';
type Result<T> = { ok: true; value: T } | { ok: false; error: Error };
type Id = string | number;
Interfaces cannot express a union of primitives or a union of object variants as the interface itself (you can still use interfaces inside a union).
Tuples, primitives, mapped wrappers:
type Pair = [string, number];
type ReadonlyUser = Readonly<UserT>;
type Flags = { [K in 'a' | 'b']: boolean };
Function type alias:
type ClickHandler = (event: MouseEvent) => void;
(Interfaces can describe call signatures too, but type aliases read more naturally for pure functions.)
What only interface does well
Declaration merging — same name, multiple blocks combine:
interface Window {
__APP_VERSION__?: string;
}
Library authors use this for augmentation. App code rarely needs merging; accidental merges can surprise you.
extends with clear OOP-style hierarchies:
interface Timestamped {
createdAt: string;
}
interface Entity extends Timestamped {
id: string;
}
type uses intersections:
type Entity = Timestamped & { id: string };
Both are valid; interfaces can error more clearly on conflicting extends in some cases.
Extending vs intersecting
interface A {
x: string;
}
interface B extends A {
y: number;
}
type C = A & { y: number };
For simple object extension, prefer one style. Intersection can produce never fields when properties conflict oddly — know how to read that error.
React props convention
Many codebases:
type ButtonProps = {
variant?: 'primary' | 'secondary';
children: React.ReactNode;
onClick?: () => void;
};
Others use interface ButtonProps. React’s own types historically lean interfaces for merging. Either is fine; don’t mix randomly in one component folder.
Performance / checking myths
Old lore said interfaces check faster. For app-sized codebases the difference is noise. Choose for expressiveness and team consistency, not microbenchmark folklore.
Practical default (frontend)
| Use case | Prefer |
|---|---|
| Object props / domain models | type or interface (team rule) |
| Unions, discriminated states | type |
| Tuples, primitives, utilities | type |
| Public lib augmentation | interface merge |
| Mapped / conditional compositions | type |
A solid team rule: type by default; interface when you need merging or a public extendable contract.
Footguns
- Merging accidentally — two
interface Userin a project combine; withtypeyou get a duplicate identifier error (often better). interfacefor unions — can’t; switch totype.- Religious rewrites — converting 200 interfaces to types in a PR adds risk for no product value.
Interview out-loud answer
“For object shapes they’re nearly the same. I use type for unions, tuples, and mapped types. I use interface when declaration merging or a clearly extendable public object contract matters. Consistency beats pedantry.”
Related on this site
- Basic types and annotations
- Union and intersection types
- Discriminated unions for UI state
- Utility types Partial Pick Omit
- Typing React props
Further reading
Related guides
- Basic Types and AnnotationsPrimitives, arrays, objects, and function annotations TypeScript actually checks — plus where inference is enough and where it is not.
- Branded Types for IDsNominal-style UserId vs OrderId in TypeScript — prevent ID mixups at compile time with brands, parsers, and form boundaries.
- Conditional Types IntroT extends U ? X : Y — how TypeScript picks types from conditions, distributes over unions, and powers utility types you already use.
- Declaration Files and DefinitelyTypedHow .d.ts files describe JS to TypeScript, when to use @types packages, module augmentation, and writing minimal ambient types for untyped libs.
- Discriminated Unions for UI StateModel loading, success, and error as mutually exclusive variants so TypeScript and your UI cannot show impossible states.