`type` vs `interface` in TypeScript

Both type and interface define shapes for objects in TypeScript, but they have important differences.

Key Differences

Declaration Merging

Interfaces can be merged when declared multiple times, types cannot:

interface User { name: string; } interface User { age: number; } // Merged into: { name: string; age: number; } const user: User = { name: 'Alice', age: 30 }; type Product = { id: string }; type Product = { price: number }; // ❌ Error: Duplicate identifier

Extending/Inheritance

Both can extend, but with different syntax:

// Interface extending interface Animal { species: string; } interface Dog extends Animal { breed: string; } // Type intersection type Animal = { species: string; }; type Dog = Animal & { breed: string; };

Union and Advanced Types

Types are more flexible for unions, primitives, and computed properties:

// ✅ Types can do unions type Status = 'pending' | 'approved' | 'rejected'; type ID = string | number; // ❌ Interfaces cannot represent unions directly interface Status { /* can't do this */ } // ✅ Types can use mapped/conditional types type ReadOnly<T> = { readonly [K in keyof T]: T[K]; }; // ✅ Types can alias primitives type Email = string;

Practical Examples

Domain Models (prefer interfaces)

// Good for domain entities interface Order { id: string; userId: string; items: OrderItem[]; total: number; } interface OrderItem { productId: string; quantity: number; price: number; } // Easy to extend in different modules interface Order { createdAt: Date; updatedAt: Date; }

API Response Types (prefer types)

// Flexible for various response shapes type ApiResponse<T> = { success: true; data: T } | { success: false; error: string }; type OrderResponse = ApiResponse<Order>; // Usage const handleResponse = (response: OrderResponse) => { if (response.success) { console.log(response.data); } else { console.error(response.error); } };

Event Payloads (prefer types)

// Good for discriminated unions in event-driven systems type DomainEvent = | { type: 'OrderCreated'; payload: { orderId: string; userId: string } } | { type: 'OrderShipped'; payload: { orderId: string; trackingNumber: string } } | { type: 'OrderCancelled'; payload: { orderId: string; reason: string } }; const handleEvent = (event: DomainEvent) => { switch (event.type) { case 'OrderCreated': // TypeScript knows payload structure here console.log(event.payload.orderId); break; // ... } };

Pros and Cons

Interface Pros:

  • Better error messages (often more readable)
  • Declaration merging useful for extending third-party types
  • Slightly better performance in type checking
  • More familiar to developers from OOP backgrounds

Interface Cons:

  • Cannot represent unions, primitives, or complex types
  • Less flexible for functional patterns
  • Cannot use computed properties

Type Pros:

  • Can represent anything (unions, primitives, tuples, etc.)
  • More powerful for functional and generic programming
  • Can use mapped and conditional types
  • Better for discriminated unions

Type Cons:

  • No declaration merging
  • Error messages can be more complex
  • Can be overused, making code less readable

Recommendation

Many teams adopt a rule: “Use interfaces for object shapes, types for everything else.”

MIT 2026 © Daniel Guo.