What is Declaration Merging in TypeScript?
Learn what declaration merging is in TypeScript, how interfaces combine automatically, module augmentation examples, and conflict rules with code samples.
Expected Interview Answer
Declaration merging is TypeScript's behavior of combining two or more separate declarations that share the same name into a single definition, most commonly used with interfaces, namespaces, and module augmentation.
If you write `interface Window { greeting: string }` in two different files, TypeScript merges them into one `Window` interface with both properties, rather than throwing a duplicate-identifier error like it would for two `type` aliases with the same name. This is the exact mechanism libraries use to let consumers extend global types — for example, augmenting Express's `Request` interface to add a custom `user` property, or extending the global `Window` object with a third-party script's global variable. Merging also works between a namespace and a class/function/enum of the same name, letting you attach static-like members. Merging follows specific rules: function/method members are overloaded (merged), while non-function properties must not conflict in type, or TypeScript raises an error.
- Lets consumers extend library types without modifying the library's source
- Standard pattern for augmenting global objects like `Window` or Node's `globalThis`
- Enables framework typings (e.g. Express `Request`) to be extended per-project
- Merges function overloads cleanly across split declarations
- Only works with interfaces/namespaces, keeping `type` aliases predictably closed
AI Mentor Explanation
Declaration merging is like a club's player list being updated by two federation offices that both add entries under the same club name, and the league combines both submissions into one roster. As long as neither office lists conflicting details for the same player, the merge works cleanly, exactly like TypeScript combining two `interface Window` declarations whose properties don't conflict.
Step-by-Step Explanation
Step 1
Declare the interface twice
`interface Window { a: string }` in one file and `interface Window { b: number }` in another are automatically combined.
Step 2
TypeScript merges members
The resulting `Window` type has both `a: string` and `b: number`, as if written in a single declaration.
Step 3
Conflicting properties error
If both declarations gave `a` a different, incompatible type, TypeScript raises a conflicting-declaration error rather than merging silently.
Step 4
Function overloads merge
Merged function/method members are treated as overloads, combining into one overloaded signature rather than conflicting.
Step 5
Apply to module augmentation
Libraries use this to let consumers write `declare module 'express' { interface Request { user?: User } }` and extend third-party types safely.
What Interviewer Expects
- Explains that only interfaces (and namespaces) merge, not `type` aliases
- Can give a real example like augmenting Express's `Request` or global `Window`
- Knows conflicting non-function property types cause a compile error
- Understands function/method members merge as overloads
- Can explain why this is useful for extending third-party library types
Common Mistakes
- Trying to declaration-merge two `type` aliases and expecting it to work
- Assuming any two interfaces with the same name merge silently even with conflicting types
- Confusing declaration merging with class inheritance or interface extension
- Forgetting `declare module` augmentation needs to target the exact original module path
Best Answer (HR Friendly)
“Declaration merging is a TypeScript feature that automatically combines multiple declarations sharing the same name into one, most often used with interfaces. It's how developers safely add extra properties to types from external libraries, like adding a custom field to Express's request object, without editing that library's source code.”
Code Example
interface Window {
greeting: string;
}
interface Window {
version: number; // merges with the previous declaration
}
const w: Window = { greeting: 'hi', version: 2 }; // both properties required// express.d.ts
declare module 'express' {
interface Request {
user?: { id: string };
}
}
// Now app.get('/', (req, res) => { req.user?.id }) type-checks project-wideFollow-up Questions
- Why can't you declaration-merge two `type` aliases the same way?
- How would you safely add a custom property to Express's Request type?
- What happens if two merged interfaces declare the same property with conflicting types?
- How do function members behave differently from data properties during merging?
- How does declaration merging relate to namespace-and-class merging?
MCQ Practice
1. Which TypeScript construct supports declaration merging?
Interfaces (and namespaces) merge automatically when declared multiple times with the same name; type aliases do not support this.
2. What happens if two merged interfaces declare the same non-function property with conflicting types?
Non-function property conflicts across merged interface declarations are a compile-time error, unlike function members which merge as overloads.
3. What is a common real-world use of declaration merging?
Declaration merging via module augmentation lets consumers extend library interfaces, such as adding a `user` property to Express's `Request`.
Flash Cards
What is declaration merging? — TypeScript combining multiple declarations sharing the same name into a single definition.
Which constructs support declaration merging? — Interfaces and namespaces — not type aliases.
What happens with conflicting non-function properties across merges? — TypeScript raises a compile-time error rather than silently picking one.
Give a real use case for declaration merging. — Module augmentation, e.g. adding a custom `user` property to Express's `Request` interface.