TypeScript's type vs. interface: The Six Differences That Decide Your Code
Choosing the wrong construct locks a codebase out of declaration merging when it's needed or forces awkward workarounds for union and tuple types. Knowing the six differences — especially the `never` trap in type intersections and the error behavior in interface extension — prevents bugs that compile silently and surface only at runtime.
The core split is philosophical: `interface` defines an open, extensible contract that supports declaration merging, while `type` is a closed alias built for composition and type-level computation. That distinction plays out across six concrete dimensions — from union and tuple support to how each handles conflicting property types during extension.
When `interface` extends a parent with an incompatible same-name property, TypeScript throws an error immediately. A `type` intersection with the same conflict silently resolves to `never`. Declaration merging is exclusive to `interface` and is the only way to safely augment third-party or global types like `Window`.
In practice, `type` dominates everyday business logic because it handles unions, mapped types, and conditional types that `interface` cannot express. `interface` remains essential for public API contracts, library type declarations, and any surface that needs to stay open for downstream augmentation.
The `never` resolution in type intersections is a silent footgun: code compiles but the property becomes unusable, with no warning that a logic error occurred.
Declaration merging is simultaneously `interface`'s superpower and its design constraint — it makes global augmentation possible but also means an interface is never truly closed, which can complicate reasoning in large codebases.
The shift toward `type` in daily use reflects a broader TypeScript trend: developers are treating the type system as a programming layer, not just a contract-definition tool, and `type` is the native language of that layer.