interface vs type in TypeScript
The real technical differences between interface and type — declaration merging, extends vs intersections, and when each shines.
3 min read
interface and type overlap enough that it's tempting to treat them as interchangeable — a lot of code that uses one could be rewritten with the other and still work. But there are real, specific differences, and understanding them (rather than picking one at random) matters once a codebase gets large.
What both can do
For a plain object shape, they look nearly identical:
interface UserI {
name: string;
age: number;
}
type UserT = {
name: string;
age: number;
};Both support optional properties (age?: number), readonly properties, extending/combining with another shape, and generics. For this common case, the choice mostly comes down to team convention.
Where type goes further: unions, primitives, tuples
type can name things that aren't object shapes at all — interface can only describe the shape of an object (or function/class), never a union or a primitive alias:
type Status = "pending" | "active" | "closed"; // union of string literals
type ID = string | number; // union of two primitive types
type Point = [number, number]; // tuple
// None of these are possible with `interface` -- it has no equivalent syntaxIf you need a union, a primitive alias, or a tuple type, type is the only option — this alone rules interface out for a large share of real-world type definitions.
Where interface goes further: declaration merging
As covered in the previous lesson, declaring the same interface name twice merges both declarations into one combined shape. type explicitly disallows this — redeclaring the same type alias name is a compile error:
type User = { name: string };
type User = { age: number }; // Duplicate identifier 'User'Declaration merging is a real, intentional feature — it's how you can extend a third-party library's types (like adding a custom property to Express's Request type) without modifying that library's source at all. type has no equivalent mechanism.
extends vs intersection (&)
Both support combining shapes, with slightly different syntax and slightly different behavior on conflicts:
interface Base { id: number; }
interface Extended extends Base { name: string; }
type BaseT = { id: number; };
type ExtendedT = BaseT & { name: string; };interface extends gives a clearer compile error if the two shapes have an incompatible property with the same name; & intersection will instead silently collapse a conflicting property to never (a type with no possible values), which can produce a confusing error far from the actual mistake.
The practical guideline
For a plain object shape — especially one that represents a public API other code will consume or extend — prefer interface; its error messages are generally clearer, and declaration merging is there if you (or a consumer of your library) ever need it. Reach for type for anything an interface genuinely can't express: unions, tuples, mapped types, or a type built by combining and transforming other types. In practice, most TypeScript codebases use both, and the difference matters most at the moment you reach for a union or a tuple and realize interface isn't an option.
Both let you mark individual properties as optional or unchangeable — covered next.