interface vs type.
The one real difference.
You've read conflicting advice. Half the internet says "always use interface," the other half says "just use type, it's more flexible." Both are wrong. There's exactly one functional difference between them, and once you know it, the decision is automatic. This page shows you the difference, the decision rule, and when each wins.
Short answer: Use interface for object shapes that might be extended or merged (public APIs, library types, React props). Use type for everything else — unions, tuples, function signatures, primitives, mapped types. The one real difference: interface supports declaration merging (defining it twice combines), type does not. In app code that never needs merging, they're interchangeable — pick one convention and stay consistent.
✅ Solution 1 — The one real difference: declaration merging
Everything else is interchangeable. This is not.
If you define two interfaces with the same name, TypeScript combines them into one type. This is called declaration merging. It's how libraries let you augment their types — e.g. adding a custom field to Express's Request or Next.js's NextPageContext. Type aliases can't do this — defining the same type name twice is a compile error.
// ─── Interfaces MERGE ──────────────────────── interface Config { host: string; } interface Config { port: number; } // Result: { host: string; port: number } // Both declarations combine. This is the whole reason interface exists. const cfg: Config = { host: 'localhost', port: 3000 }; // ─── Types ERROR on redeclare ──────────────── type Config2 = { host: string; }; // type Config2 = { port: number }; // ❌ Duplicate identifier 'Config2'. Types can't merge. // ─── Real-world: augmenting library types ──── // This is how libraries let you extend their types. // A library defines: // // export interface Request { // body: unknown; // params: Record; // } // // You augment it (in a .d.ts file): // // declare module 'express' { // interface Request { // user?: User; // } // } // // Now every Request has user. This only works with interface. console.log(cfg.host, cfg.port);
// Interfaces and types are erased entirely. // The merging happens at compile time only. const cfg = { host: 'localhost', port: 3000 }; console.log(cfg.host, cfg.port);
This is the ONLY functional difference. Everything else is style. Interfaces can extend with extends; types use &. Interfaces can describe object shapes; types can describe object shapes and unions and tuples and primitives. But none of those difference matter unless you need merging. In 95% of app code, interface X { a: string } and type X = { a: string } are identical — same compile output, same error messages, same autocomplete. The merging behavior is the only thing that forces the choice.
✅ Solution 2 — When type is strictly required
Unions, tuples, primitives — interface can't express these.
Interfaces can only describe object shapes — keys and values. Types can describe any TypeScript type: unions, tuples, primitives, function signatures, mapped types, conditional types. If your type isn't an object shape, it must be type. There's no alternative.
// ─── Unions — type only ────────────────────── type Status = 'idle' | 'loading' | 'success' | 'error'; // interface Status = 'idle' | 'loading'; // ❌ syntax error type ID = string | number; type Result = { ok: true; data: string } | { ok: false; error: string }; // interface can't express a union of shapes either // ─── Tuples — type only ────────────────────── type Point = [number, number]; type Entry = [string, number]; // interface Point = [number, number]; // ❌ syntax error // ─── Function types — type only ────────────── type Handler = (e: MouseEvent) => void; type Comparator= (a: T, b: T) => number; // interface Handler = (e: MouseEvent) => void; // ❌ syntax error // ─── Primitives — type only ────────────────── type UserID = string; type Score = number; // interface UserID = string; // ❌ syntax error // ─── Mapped types — type only ──────────────── type Readonly = { readonly [K in keyof T]: T[K] }; // interface Readonly { ... } // ❌ can't use mapped syntax // ─── The pattern ───────────────────────────── // If it's an object shape → interface or type (your choice) // If it's anything else → type is required const s: Status = 'loading'; const p: Point = [10, 20]; console.log(s, p);
// All type aliases erased. const s = 'loading'; const p = [10, 20]; console.log(s, p);
Unions
type Status = 'idle' | 'loading' — interface can't express a union. If your type is "one of these," it's a type alias, full stop.
Tuples
type Point = [number, number] — fixed-length arrays. Interface has no syntax for this.
Function signatures
type Handler = (e: Event) => void — interface can't describe a function type directly.
Mapped / conditional
type Readonly — mapped type syntax is type-only.
✅ Solution 3 — When interface is preferred
Object shapes that might be extended or augmented.
When the type is an object shape and might be extended by others (or is part of a public API), interface is the better default. It gives you extends syntax, slightly cleaner error messages, and — critically — the option for consumers to merge their own fields via declaration merging. Libraries depend on this.
// ─── Public object shape — use interface ───── interface User { id: string; name: string; email: string; } // ─── Extendable — interface reads cleaner ──── interface Admin extends User { role: 'admin'; permissions: string[]; } // Equivalent with type: type Admin2 = User & { role: 'admin'; permissions: string[]; }; // ─── Both work. Interface reads slightly better // for hierarchical shapes. Use interface here. // ─── Multi-extends (interface only) ────────── interface Timestamps { createdAt: Date; updatedAt: Date; } interface Post extends User, Timestamps { title: string; body: string; } // ─── Real-world: React props ───────────────── // React's Props types are interfaces so you can // augment them in your own .d.ts. This is the pattern: // // interface Theme { // primary: string; // } // // // later, in another file: // interface Theme { // secondary: string; // merged // } const admin: Admin = { id: '1', name: 'Alice', email: 'a@x.com', role: 'admin', permissions: ['read'] }; console.log(admin.role);
// Interfaces erased. Only the runtime object remains. const admin = { id: '1', name: 'Alice', email: 'a@x.com', role: 'admin', permissions: ['read'] }; console.log(admin.role);
Hierarchical shapes
interface Admin extends User reads more naturally than type Admin = User & {...}. Minor, but real.
Multi-extends
Interface can extend multiple parents: interface Post extends User, Timestamps. With type, you'd use multiple & — equivalent but noisier.
Library APIs
Libraries type their public APIs with interfaces so users can augment via declaration merging. React, Express, Next.js all rely on this.
Better errors
Interface errors are slightly cleaner — the compiler can name the interface rather than expand its structure. Marginal, but noticeable in large codebases.
⚠️ Common mistakes
The interface-vs-type traps that waste real time.
Half your code uses type User = {...}, half uses interface User {...}. No reason — just history. Fix: pick one convention. Most teams use interface for object shapes, type for everything else.
interface Status = 'idle' | 'loading' — syntax error. Interfaces can only describe object shapes. Fix: type Status = 'idle' | 'loading'.
Naming two interfaces Config in different files silently merges them. Sometimes that's what you want (augmentation), often it's a bug. Fix: check your codebase for duplicate interface names.
type Admin = User & { role: 'admin' } works, but interface Admin extends User reads better for hierarchical shapes. Fix: interface for "is a" relationships.
They can — with &. type Admin = User & { ... }. The result is equivalent to interface extends. Fix: use whichever reads better for the case.
interface Handler = (e: Event) => void — syntax error. Fix: type Handler = (e: Event) => void.
Compile time and runtime performance are essentially identical. Don't choose based on performance — there's no meaningful difference.
"Always use interface" and "always use type" are both wrong. Both have cases. Fix: know the one real difference (merging) and pick accordingly.
❓ Frequently asked
What is the difference between interface and type in TypeScript?
Both describe types and are interchangeable for object shapes in 95% of cases. The one functional difference: interface supports declaration merging (defining the same interface twice combines them), type does not. Also, type can express unions, tuples, and conditional types — interface can only describe object shapes.
When should I use interface instead of type?
Use interface for public object shapes that might be extended with extends, or that other code might need to augment via declaration merging. Use type when describing unions, tuples, function signatures, mapped types, or any non-object type.
What is declaration merging?
Declaration merging lets you define an interface multiple times and have all definitions combine into one. interface Config { a: string } and interface Config { b: number } merge to give Config both a and b. This is how libraries let you extend their types. Type aliases cannot merge.
Does it matter which one I use?
For most code, no — the compiler produces identical output and error messages are nearly equivalent. The choice matters for library authors who want consumers to augment their types (use interface) and for unions, tuples, and complex types (type only). For app code, consistency within the project matters more than the choice itself.