Types vs Interfaces in TypeScript — Which One Should You Use?
The real differences between type and interface, when each one wins, and a practical decision guide
Types vs Interfaces in TypeScript
This is probably the most asked TypeScript question ever. And most answers online are either outdated or oversimplified.
Let's settle it once and for all.
The Basics
Both can describe the shape of an object:
So far, identical. Here's where they diverge.
What Only type Can Do
Union Types
type Status = "idle" | "loading" | "success" | "error";
type StringOrNumber = string | number;
// ❌ Interfaces can't do this// interface Status = "idle" | "loading" // Syntax errorMapped Types
type Readonly<T> = { readonly [K in keyof T]: T[K];};
type Optional<T> = { [K in keyof T]?: T[K];};Tuple Types
type Coordinates = [number, number];type APIResponse = [data: User[], error: string | null];
// ❌ Interfaces can't express tuples cleanlyConditional Types
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // truetype B = IsString<42>; // falseExtracting Types from Values
const config = { apiUrl: "https://api.example.com", timeout: 5000, retries: 3,} as const;
type Config = typeof config;// { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000; readonly retries: 3 }What Only interface Can Do
Declaration Merging
// First declarationinterface Window { title: string;}
// Second declaration — merges with the first!interface Window { customProperty: boolean;}
// Window now has both `title` and `customProperty`const w: Window = { title: "My App", customProperty: true,};This is actually useful for extending third-party types (like adding properties to Window or Request).
Types can't merge:
type User = { name: string };type User = { email: string }; // ❌ Error: Duplicate identifierExtends (Cleaner Inheritance)
interface Animal { name: string; age: number;}
interface Dog extends Animal { breed: string; bark(): void;}
// Types use intersection instead:type Cat = Animal & { indoor: boolean; purr(): void;};Both work, but extends gives better error messages when types conflict.
Performance Difference
Here's something most people don't know — interfaces are slightly faster for the TypeScript compiler.
// ✅ Interface — compiler caches the shapeinterface UserProps { name: string; avatar: string; role: "admin" | "user";}
// 🐢 Type intersection — evaluated every timetype UserProps = BaseProps & { role: "admin" | "user";} & AvatarProps;For small projects you'll never notice. For large codebases with hundreds of types, interface caching can make a difference in IDE responsiveness and compile times.
The Decision Guide
My Recommendation
Here's what I actually do in production:
// ✅ Use interface for object shapes and React propsinterface ButtonProps { label: string; variant: "primary" | "secondary"; onClick: () => void;}
// ✅ Use type for everything elsetype Status = "idle" | "loading" | "success" | "error";type Handler = (event: MouseEvent) => void;type Nullable<T> = T | null;type APIResponse<T> = { data: T; error: string | null; status: number;};The simple rule: Use interface for objects and props, use type for unions, utilities, and anything complex.
Don't overthink it. Both are fine. Pick one convention for objects and stick with it across your codebase. Consistency beats the marginal difference.
Common Gotchas
Gotcha 1: extends vs & Error Messages
interface A { x: number;}
// Interface extends — clear errorinterface B extends A { x: string; // ❌ Error: Type 'string' is not assignable to type 'number'}
// Type intersection — silent problemtype C = A & { x: string; // No error... but x is now `never` (number & string = never)};Interfaces catch conflicts. Intersections hide them.
Gotcha 2: Interfaces Are Open
// Someone else's code or a library:interface Config { debug: boolean;}
// Your code accidentally merges into it:interface Config { debug: string; // 💥 Now debug is boolean AND string}This is why some teams prefer type for internal code — it's sealed by default.
TL;DR
interface= objects, props, class shapes, public APIstype= unions, tuples, mapped types, utilities, complex types- Performance: interfaces are slightly faster in the compiler
- Safety: types can't be accidentally merged
- Just pick one for objects and be consistent
Both are great. The worst choice is switching randomly between them with no convention.
Stay Updated 📬
Get the latest tips and tutorials delivered to your inbox. No spam, unsubscribe anytime.