interface and type appear interchangeable. However, their underlying mechanics—such as Declaration Merging, compiler type-checking performance, and recursive relationship resolution—are fundamentally distinct.1. Feature-by-Feature Matrix#
| Feature | interface | type Alias |
|---|---|---|
| Object Shapes | ✅ Supported | ✅ Supported |
Primitives (string, number) | ❌ Impossible | ✅ Supported (type ID = string) |
Union Types (A | B) | ❌ Impossible | ✅ Supported |
Tuples ([number, string]) | ❌ Impossible | ✅ Supported |
| Declaration Merging | ✅ Supported | ❌ Throws Duplicate identifier |
| Extension Syntax | interface A extends B | type A = B & C (Intersection) |
Global Augmentation (Window) | ✅ Supported | ❌ Impossible |
| Compiler Performance | Faster (Flat Type Map) | Slower on deep & intersections |
2. Declaration Merging (The Interface Superpower)#
If you declare an interface multiple times with the same identifier in the same scope (or globally), TypeScript automatically merges all property definitions into a single interface.
// First declaration of User
interface User {
id: string;
name: string;
}
// Second declaration of User (same scope or imported augmentation)
interface User {
email: string;
role: "admin" | "user";
}
// The resulting 'User' interface automatically contains ALL 4 fields!
const user: User = {
id: "usr_101",
name: "Alice",
email: "[email protected]",
role: "admin",
};Global Augmentation Example (Express / Window)#
Declaration merging is the official mechanism for extending 3rd-party node modules or browser APIs:
// Augmenting global Window interface in a browser app
declare global {
interface Window {
__INITIAL_STATE__: Record<string, unknown>;
}
}
window.__INITIAL_STATE__ = { theme: "dark" }; // 🟢 WORKED!
Why Type Aliases Cannot Merge#
If you attempt the same pattern using type, TypeScript immediately flags a compile-time error:
type AppConfig = {
env: string;
};
// ❌ Compiler Error: Duplicate identifier 'AppConfig'.
/*
type AppConfig = {
port: number;
};
*/3. Extension Mechanics: extends vs Intersection (&)#
interface Extends (Strict Type Validation)#
When an interface extends another interface, TypeScript validates that child properties do not conflict with parent properties. If there is an incompatible type override, TS flags an error at the extension site:
interface Parent {
id: string;
}
// ❌ Compiler Error: Interface 'Child' incorrectly extends interface 'Parent'.
// Types of property 'id' are incompatible (number vs string).
/*
interface Child extends Parent {
id: number;
}
*/type Intersection (&) (Loose Type Merging)#
When intersecting two type aliases using &, TypeScript silently merges the properties. If property types conflict, it evaluates the conflicting property to never:
type ParentType = {
id: string;
};
type ChildType = ParentType & {
id: number; // Conflicting property!
};
// ChildType is created without errors, BUT 'id' becomes 'never'!
// ❌ Error: Type 'number' is not assignable to type 'never'.
// const c: ChildType = { id: 101 };
4. Compiler Performance Mechanics (tsc)#
In massive TypeScript codebases (100,000+ lines of code), using interface over type intersections (&) provides measurable compilation speed improvements.
Why interface Compiles Faster:#
- Flat Structure Caching:
interfacecreates a flat property map intscinternal memory. Properties are cached by name, allowing $O(1)$ property lookup during type checking. - Intersection Resolution Overhead:
type A & B & Cforces the compiler to evaluate deep recursive relationship graphs and detect property collisions across every nested child type on every type-check pass.
If you are generating deep object models, use interface extends for better tsc performance.
5. The Decision Matrix: When to Use Which?#
Use interface when:#
- Defining Object Shapes or Class Contracts (
implements). - Authoring Public Libraries or SDKs where consumers might need to augment type definitions via Declaration Merging.
- Optimizing
tsccompilation performance for large object models.
Use type when:#
- Defining Unions (
type Status = "idle" | "success"). - Defining Primitives, Tuples, or Functions (
type Handler = () => void). - Performing Type Transformations (Mapped Types, Conditional Types,
keyof,infer).
Summary & Next Steps#
In this episode:
- We analyzed Declaration Merging in interfaces vs duplicate identifier errors in type aliases.
- We compared strict
interface extendserror checking vs loosetype &intersection property collisions. - We evaluated
tsccompiler performance: interfaces use flat property maps for faster type checking. - We constructed a complete decision matrix for professional type design.
In Episode 26: Indexed Access Types, we will explore how to query property types from existing object interfaces using T[K] syntax!

