interface User { name: string; age: number; }
type UserT = { name: string; age: number; };
For plain object shapes, these are nearly interchangeable, and this is one of the most commonly asked "what's the difference" questions in TypeScript interviews. The practical differences:
interface | type | |
|---|---|---|
| Object shapes | Yes | Yes |
Unions ("a" | "b") | No | Yes |
| Primitives/tuples | No | Yes |
| Extending another shape | extends | & (intersection) |
| Re-opening after declaration | Yes — declaration merging | No — a duplicate name is an error |
| Typical use | Public object/class contracts meant to be extended or implemented | Unions, tuples, primitives, and everything not naturally "object-shaped" |
Declaration merging is the one behavioral difference worth knowing in depth: re-declaring an interface with the same name adds to it rather than conflicting.
interface Window {
myGlobal: string;
}
interface Window {
anotherGlobal: number;
}
// Window now effectively has both myGlobal and anotherGlobal
This is exactly how libraries let you extend global or third-party types (augmenting express's Request, or the global Window) without forking their source — and exactly why it can bite you by accident if two unrelated files declare an interface with the same name, expecting two separate types, and instead silently get one merged type.