Declaration Merging
Declaration merging is TypeScript's rule that two or more declarations sharing the same name are automatically combined into one definition, letting you extend interfaces, attach statics to functions, and augment library or global types.
Learn Declaration Merging in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Think of declaration merging like adding pages to a shared binder. Two people each bring a few pages labelled "Box"; instead of fighting over the label, TypeScript files all the pages under "Box" so the binder now contains everyone's contributions. That's why a library can ship a base Request interface and your code can quietly slip in an extra user page — same label, combined contents.
1. Function + Namespace Merging
When a function and a namespace share a name, TypeScript merges them: the function stays callable and also gains the namespace's exported members as static properties. This is the type-safe way to describe a callable value that carries helpers — like jQuery's $ being both a function and a bag of utilities. At runtime it's simply a function object with extra properties attached.
2. Interface Merging & Augmenting Globals
Two interfaces with the same name merge their members into one larger interface — there's no duplicate error, unlike with type aliases. This is the engine behind global augmentation : you reopen a built-in interface like and add your own properties so the compiler knows about values you attach at runtime.
3. Module Augmentation (Express & Beyond)
To extend types that live in another package, use module augmentation . Wrapping a declaration in (or {'declare global { namespace Express }'} ) merges new members into that library's interfaces. The canonical use is teaching TypeScript about req.user set by your auth middleware.
🎯 Your Turn
Build your own function + namespace merge: a callable counter() that bumps a count, plus a .reset() static. Fill in the two blanks marked ___ , then run it.
No blanks this time — just a brief and a starting outline. Build the function + namespace merge yourself, run it, and check your output against the example in the comments.
Practice quiz
What is declaration merging in TypeScript?
- Combining two .ts files into one
- Merging types at runtime with Object.assign
- Two or more declarations with the SAME name combined into one definition
- Importing everything from another module
Answer: Two or more declarations with the SAME name combined into one definition. Declaration merging combines same-named declarations (interfaces, namespaces, etc.) into a single definition.
What happens when you declare two interfaces with the same name?
- Their members are combined into one interface
- A duplicate-identifier error
- The second replaces the first
- Only the first is used
Answer: Their members are combined into one interface. Same-named interfaces merge: their members are combined, unlike type aliases.
Can two type aliases with the same name be merged?
- Yes, just like interfaces
- Only if they are in different files
- Only with declare global
- No, it is a duplicate-identifier error
Answer: No, it is a duplicate-identifier error. Type aliases do NOT merge; declaring the same 'type' twice is a duplicate-identifier error.
When a function merges with a same-named namespace, the result is:
- Two separate values
- A callable value that also carries static members
- Just a namespace
- A compile error
Answer: A callable value that also carries static members. The merged value stays callable as a function and gains the namespace's exports as statics.
Which construct do you use to add a property like req.user to Express's Request type?
- declare global with namespace Express { interface Request { ... } }
- A type alias
- Object.defineProperty
- A class
Answer: declare global with namespace Express { interface Request { ... } }. Global declaration merging via 'declare global { namespace Express { interface Request { user?: User } } }' adds the typing.
What does declaration merging add at runtime?
- A reverse-mapped object
- A new constructor
- Nothing — it only adds type information
- A frozen prototype
Answer: Nothing — it only adds type information. Merging is purely type-level; you still must assign the actual property (e.g. greet.prefix) at runtime.
Why must an augmentation file be a module (have an import or export)?
- For faster compilation
- Otherwise declare global is not picked up and the augmentation is ignored
- To enable strict mode
- To allow default exports
Answer: Otherwise declare global is not picked up and the augmentation is ignored. Global augmentations only work inside module files; add 'export {}' so the file is treated as a module.
Which keyword targets a specific third-party module for augmentation?
- declare global
- import type
- namespace
- declare module "express"
Answer: declare module "express". Module augmentation uses 'declare module "lib" { ... }' to merge members into another package's types.
For a public, library-facing shape that consumers may extend later, you should prefer:
- A type alias
- An interface (it is open and mergeable)
- A const
- An enum
Answer: An interface (it is open and mergeable). Interfaces are open, so a library and its consumers can both contribute members via merging.
Which combination CANNOT merge?
- Two same-named interfaces
- A function and a namespace
- Two same-named type aliases
- An enum and a namespace
Answer: Two same-named type aliases. Interfaces, function+namespace, and enum+namespace merge; two type aliases of the same name do not.