TypeScript's type vs. interface: The Rules That Actually Matter
Picking `type` over `interface` — or vice versa — is not just stylistic. Using `type` for a global augmentation silently fails; using `type` for conflicting property extensions silently produces `never` instead of a compile error, hiding bugs that `interface` would catch immediately.
Both `type` and `interface` describe object structures and function signatures, and both support extension — `interface` via `extends`, `type` via intersection `&`. The overlap is broad enough that many codebases mix them without a clear rule. But the differences are concrete and consequential. `interface` supports declaration merging: multiple same-name declarations combine into one type, which is essential for patching global types like `Window`. `type` forbids duplicate names entirely. `type` can alias primitives, unions, and tuples; `interface` is restricted to object-like structures. For function types, `interface` uses a call-signature syntax that makes attaching extra properties natural, while `type` uses a cleaner arrow syntax but requires intersection for the same effect. The most dangerous difference is conflict behavior: `interface` throws a compile error on incompatible property extensions, but `type` silently resolves the conflict to `never` — a type that accepts no value. The practical rule of thumb that emerges is to use `interface` for public library APIs and global augmentations, and `type` for internal aliases, unions, and simple function signatures.
The `never` resolution in `type` intersections is a footgun: it compiles without complaint but makes the field unusable, so a team that defaults to `type` for everything risks shipping types that look correct but reject every value.
Declaration merging is the single capability that makes `interface` non-negotiable for library authors and global augmentations — there is no `type` workaround, so the choice is forced, not preferential.
The community convention of `interface` for public APIs and `type` for internals is less about dogma and more about surfacing errors early: `interface` catches extension conflicts at compile time, which matters most at API boundaries.