Skip to Content
DocsNode.js`type` vs `interface` in TypeScript

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.”

Last updated on