TypeScript Types vs Interfaces: The Definitive Guide
The single most common TypeScript interview question. Here's everything you need to know.
What They Have In Common
Both describe the shape of objects and are structurally equivalent for most everyday use:
// These are identical in usage:
type UserType = { id: number; name: string }
interface UserInterface { id: number; name: string }
function greet(user: UserType) { / works / } function greet(user: UserInterface) { / also works / }
Both can be extended, both appear in IDE tooltips, both enforce structural compatibility.
Key Differences
### 1. Declaration Merging — Interface Only
interface Window {
myCustomProperty: string
}
interface Window {
anotherProperty: number
}
// Window now has both properties — merged!
type Config = { debug: boolean } type Config = { verbose: boolean } // ❌ Error: duplicate identifier
This is the killer feature for extending third-party types:
// In your express app: declare module 'express-serve-static-core' { interface Request { user?: AuthenticatedUser } } // Now req.user is typed everywhere
### 2. Union and Intersection — Type Only
type StringOrNumber = string | number // union
type AdminUser = User & { permissions: string[] } // intersection
type Callback = (err: Error | null) => void // function type
type Pair<T> = [T, T] // tuple
Interfaces cannot express any of these directly.
### 3. Computed / Mapped Types — Type Only
type Keys = keyof User // 'id' | 'name' | 'email'
type Optional = Partial<User> // all fields optional
type OnlyIds = Record<string, number>
// Interface cannot do this: type EventHandlers = { [K in keyof Events as on${Capitalize<K>}]: (e: Events[K]) => void }
### 4. Extends vs Intersection
Both can compose types, but differently:
// Interface extends — property conflicts are caught as errors
interface Animal { name: string }
interface Dog extends Animal { breed: string }
// Type intersection — conflicting properties become never type Animal = { name: string; sound: string } type Cat = Animal & { sound: 'meow' } // Cat.sound is 'meow' (intersection narrows it)
Practical Decision Guide
Use interface when:
- Defining the shape of an object or class contract
- You need declaration merging (extending third-party modules)
- Writing a library where consumers may extend types
- Defining class implements signatures
Use type when:
- Working with unions or intersections
- Using mapped types, conditional types, or template literals
- Defining function signatures (type Fn = (x: string) => void)
- Creating tuple types
- Aliasing primitives or utility type results
The Recommended Default
Most style guides say: prefer interface for object shapes, use type for everything else.
The TypeScript team's own guidance: use interface when possible; use type when you need features only type provides.
// Good: interface for object/class shapes
interface Repository<T> {
findById(id: string): Promise<T>
save(entity: T): Promise<T>
delete(id: string): Promise<void>
}
// Good: type for union and complex compositions type Result<T, E extends Error = Error> = | { success: true; data: T } | { success: false; error: E }
One Practical Tip
When in doubt about which to use, start with interface. If TypeScript complains about something interface can't do (unions, mapped types), switch to type. This matches how most TypeScript teams operate in practice.
Practice TypeScript questions at [JSPrep Pro](/auth).