Type vs Interface in TypeScript β€” The Simple Rule Most Developers Don't Know

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 β†’ type

Now 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

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