TypeScript interface vs type: What's the Difference
TypeScript's interface and type both describe object shapes, but they diverge on declaration merging, unions, and extension. When to reach for each.
TypeScript gives you two ways to describe the shape of an object: an interface or a type alias. For plain object shapes they’re nearly interchangeable, but they diverge in a few places that matter — declaration merging, what they can describe, and how error messages read. Neither is strictly better; the right choice depends on what you’re modeling.
The basic overlap
Both can describe the same object shape with identical syntax at the call site:
interface UserInterface {
id: string;
name: string;
}
type UserType = {
id: string;
name: string;
};
A function that accepts one accepts the other. TypeScript’s structural type system doesn’t care which keyword produced the shape — only whether the shape matches. If you’re new to the type system generally, TypeScript generics and the utility types built on top of Partial, Pick, and Omit work identically whether you feed them an interface or a type.
Where they diverge
Declaration merging
An interface can be declared multiple times with the same name, and TypeScript merges the declarations into one:
interface Window {
myGlobal: string;
}
interface Window {
anotherGlobal: number;
}
// Window now has both myGlobal and anotherGlobal
A type alias cannot be redeclared — doing so is a compile error. This makes interface the right tool for augmenting types you don’t own, like extending a third-party library’s ambient types or adding a property to the global Window object.
What each can express
A type alias can describe things an interface cannot: unions, tuples, mapped types, and conditional types.
type Status = "pending" | "active" | "closed";
type Pair = [string, number];
type Handler = (event: string) => void;
There’s no interface equivalent for a union of string literals or a tuple. If you’re modeling anything other than a plain object — a union, a function signature, a mapped type derived from another type — type is the only option. See discriminated unions and mapped types for common patterns that require type.
Extension syntax
Both support extension, with different syntax:
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
type AnimalType = {
name: string;
};
type DogType = AnimalType & {
breed: string;
};
interface extends checks that the extended shape is compatible and errors early with a clear message if it isn’t. Intersection (&) on type will still combine the shapes, but conflicting property types can silently collapse to never instead of raising an error at the point of extension — the mismatch only surfaces later, when you try to use the property.
Comparison table
interface | type | |
|---|---|---|
| Object shapes | Yes | Yes |
| Unions | No | Yes |
| Tuples | No | Yes |
| Declaration merging | Yes | No |
| Extension | extends (checked eagerly) | & intersection (checked on use) |
| Function/constructor signatures | Yes, via call signatures | Yes, more common in practice |
| Implements (classes) | Yes | Yes |
Which one should you use
For a plain object shape that you expect other code to extend or augment — a public API’s parameter type, a class’s implemented shape, anything a consumer of your library might need to add fields to — interface gives you merging and slightly better error messages on conflicting extensions.
For anything else — unions, tuples, function types, types derived from other types via mapping or conditionals — type is the only choice, since interface can’t express them.
A common convention is to default to interface for object shapes and reach for type only when you need a feature interface doesn’t have. Other codebases standardize on type everywhere for consistency, since it’s a strict superset of what interface can express except merging. Either convention is defensible; what matters is picking one and applying it consistently, since mixing both for the same kind of shape adds cognitive overhead without a functional benefit. If your team enforces a house style, tools like ESLint can lint for it automatically.
One thing that doesn’t matter for this decision: runtime performance. Both compile away entirely — TypeScript’s type system is erased at build time, so interface and type have zero runtime cost regardless of which you choose.
The takeaway
interface and type overlap heavily for plain object shapes, but interface supports declaration merging and gives eager error checking on extension, while type is the only option for unions, tuples, and other non-object shapes. Use interface when you’re defining an object shape that might need to merge or be extended by consumers; use type for everything else. Pick a default for plain object shapes and stay consistent — the compiler doesn’t have a preference, but your codebase’s readability benefits from having one.
Keep reading
Takina · · 4 min read TypeScript Abstract Classes, Explained
Abstract classes in TypeScript define shared implementation plus methods subclasses must fill in. How they differ from interfaces and when to reach for them.
Takina · · 3 min read TypeScript's never Type, Explained
never represents values that can't exist — it marks unreachable code, exhaustive switches, and functions that always throw or loop forever.
Takina · · 4 min read TypeScript readonly Modifiers Explained
TypeScript's readonly keyword blocks reassignment at compile time for properties, arrays, and tuples — with no runtime enforcement at all.