Type vs Interface in TypeScript β The Simple Rule Most Developers Don't Know
If you work with TypeScript daily, you've probably asked:
π Should I use type or interface?
And if you're honestβ¦ you've probably used both randomly π .
Don't worry β even experienced developers debate this.
The truth?
π Each has specific strengths that make your code cleaner, scalable, and team-friendly.
This guide cuts through the noise with clear rules you can apply immediately.
π§ The One Rule That Changes Everything
Before diving into examples, here's the mental model that senior developers use:
Interface = Contract for objects (designed to be extended)
Type = Swiss Army knife (handles everything else)
That's it. Keep reading to see why this matters.
π― The Actually Simple Rule
Can it be described with ONLY object properties?
ββ Yes β interface
ββ No β type
Will other developers extend this?
ββ Yes β interface
ββ Probably not β typeNow let's see this in action.
π¦ When to Use Interface
1οΈβ£ Object Shapes That Grow
Interfaces are built for objects that evolve over time.
typescript
interface User {
name: string;
email: string;
}
interface Admin extends User {
permissions: string[];
canDelete: boolean;
}
interface SuperAdmin extends Admin {
auditLog: AuditEntry[];
}Why this matters: Your team can safely extend User without breaking existing code.
Real-world use cases:
- API data models
- Component props in React/Vue
- Database entities
- Configuration objects
2οΈβ£ When You Want Error Messages That Actually Help
typescript
interface ApiConfig {
endpoint: string;
timeout: number;
}
function setupApi(config: ApiConfig) {
// ...
}
setupApi({ endpoint: "/api", timout: 5000 });
// ^^^^^^
// Error: Object literal may only specify known properties,
// and 'timout' does not exist in type 'ApiConfig'.
// Did you mean to write 'timeout'?Interfaces give better autocomplete and clearer error messages when working with object literals.
3οΈβ£ Library Authors & Public APIs
If you're building a library, interfaces signal "you can extend this."
typescript
// your-library/types.ts
export interface PluginConfig {
name: string;
version: string;
}
// user's code
import { PluginConfig } from 'your-library';
interface MyPluginConfig extends PluginConfig {
customOption: boolean;
}Declaration merging (extending the same interface in different files) is possible, but avoid it in application code β it makes types hard to trace.
Use it only when building extensible libraries or working with third-party types you don't control.
β‘ When to Use Type
1οΈβ£ Anything That's NOT Just an Object
typescript
// Primitives
type ID = string | number;
// Unions
type Status = 'pending' | 'approved' | 'rejected';
// Tuples
type Coordinate = [number, number];
// Functions
type Handler = (event: Event) => void;Interfaces can't do any of this. That's the key difference.
2οΈβ£ Discriminated Unions (Type-Safe State Machines)
typescript
type ApiResponse =
| { success: true; data: User[] }
| { success: false; error: string };
function handleResponse(response: ApiResponse) {
if (response.success) {
// TypeScript knows response.data exists here
console.log(response.data);
} else {
// TypeScript knows response.error exists here
console.error(response.error);
}
}This pattern eliminates entire classes of bugs. Use it for:
- API responses
- Form states (idle | loading | success | error)
- Redux actions
- Async operation results
3οΈβ£ Utility Type Transformations
typescript
interface User {
id: string;
name: string;
email: string;
password: string;
}
// Safe for API responses
type PublicUser = Omit<User, 'password'>;
// For update endpoints
type UpdateUser = Partial<User>;
// Combine multiple transformations
type CreateUserDTO = Omit<User, 'id'> & { confirmPassword: string };Types compose better with utility types like Omit, Pick, Partial, Record, etc.
4οΈβ£ Computed Property Names
typescript
type EventMap = {
[K in 'click' | 'hover' | 'focus' as `on${Capitalize<K>}`]: (e: Event) => void;
};
// Results in:
// {
// onClick: (e: Event) => void;
// onHover: (e: Event) => void;
// onFocus: (e: Event) => void;
// }Interfaces can't do mapped types. Use types when you need programmatic type generation.
π Interface vs Type: The Key Differences
| Feature | Interface | Type |
|---|---|---|
| Object shapes | β Best choice | β Works |
| Primitives/unions/tuples | β Can't do | β Best choice |
extends keyword | β Clear syntax | β οΈ Use & instead |
| Declaration merging | β Possible (use sparingly) | β Not possible |
| Computed properties | β Can't do | β Full support |
| Error messages | β Often clearer | β οΈ Can be verbose |
| Performance | β‘ Identical | β‘ Identical |
π§ͺ Real-World Example: Building an API Client
Here's how you'd structure types in a production app:
typescript
// Core data models (objects that extend) β interface
interface User {
id: string;
name: string;
email: string;
role: UserRole;
}
interface AdminUser extends User {
permissions: string[];
department: string;
}
// Enums/unions β type
type UserRole = 'user' | 'admin' | 'guest';
// API responses (discriminated unions) β type
type ApiResult<T> =
| { ok: true; data: T }
| { ok: false; error: string; code: number };
// Utility transformations β type
type CreateUserInput = Omit<User, 'id'>;
type UpdateUserInput = Partial<CreateUserInput>;
// Usage
async function fetchUser(id: string): Promise<ApiResult<User>> {
// implementation
}
```
**Pattern**: Interface for the "nouns" (entities), type for the "adjectives" (variations and states).
---
## π― Your Decision Tree (Print This)
```
β What are you defining?
ββ Object with properties only?
β ββ Will it be extended by others? β interface
β ββ Just a one-off shape? β interface or type (either works)
β
ββ Union of multiple options? β type
β
ββ Primitive/tuple/function signature? β type
β
ββ Using Omit/Pick/Partial/etc? β type
β
ββ Mapped/computed types? β typeβ Myths Debunked
"Interfaces are faster"
False. Both exist only at compile-time. Zero runtime difference.
"Always use interface for consistency"
Oversimplified. You'll force yourself into awkward patterns when you need unions or utility types.
"Types are more modern"
Misleading. Both are modern. Interfaces were designed for object-oriented patterns, types for functional patterns. Use both.
π₯ The Ultimate Guideline
Default to interface for object shapes.
Switch to type when interface can't do what you need.
That's it. Stop overthinking.
β¨ Pro Tips from Production Code
1οΈβ£ Prefer composition over deep inheritance
typescript
// β Hard to maintain
interface A extends B extends C extends D { }
// β
Clear and flexible
interface User extends Timestamped, Identifiable {
name: string;
}2οΈβ£ Name your types meaningfully
typescript
// β Vague
type Data = { ... }
// β
Descriptive
type UserProfileData = { ... }3οΈβ£ Keep interfaces focused
typescript
// β God object
interface User {
// ... 30 properties
}
// β
Split responsibilities
interface UserIdentity { }
interface UserProfile { }
interface UserSettings { }π¬ Next Steps
Now you know:
- β When each tool is the right choice
- β The actual technical differences
- β How to structure types in real projects
Action item: Review your last TypeScript file. Find one place where switching type β interface would make the code clearer.
What TypeScript topic should I break down next?
Generics? Utility types? Type guards? Let me know.