Declaration Merging in TypeScript
Declaration merging lets you declare the same interface multiple times in the same scope. TypeScript combines them into one. Learn how merging works, where it is useful, and its rules.
Declaration merging is a TypeScript feature where the compiler combines multiple declarations of the same interface into a single interface. If you declare the same interface name twice in the same scope, TypeScript does not complain. Instead, it merges the properties from both declarations into one.
This is unique to TypeScript. Most typed languages treat a duplicate declaration as an error. TypeScript uses it to model JavaScript patterns where objects are built up incrementally.
How interface merging works
At its simplest, merging joins the members of both declarations. Every property from both interfaces ends up in the merged result.
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
const box: Box = { height: 5, width: 6, scale: 10 };The merged Box requires height, width, and scale. The compiler treats it exactly as if you had declared all three properties in a single interface.
The merge happens across the entire scope. If you declare Box in two different files that are both visible in the same compilation, they still merge. This is how third-party type augmentation works -- you add properties to a library interface from your own file.
Rules for merged members
Non-function members with the same name must have identical types. If two declarations of the same interface both declare a property name but give it different types, the compiler reports an error.
interface User {
id: string;
}
interface User {
id: number;
// Error: 'id' must be of type 'string' to match the first declaration
}Function members of the same name merge as overloads. Later declarations appear earlier in the overload list, which means they take priority when TypeScript checks argument types.
interface Logger {
log(level: string, message: string): void;
}
interface Logger {
log(message: string): void;
}
// Merged overloads (later first):
// log(message: string): void;
// log(level: string, message: string): void;The single-parameter overload comes first because it was declared second. When you call log, TypeScript checks the one-parameter signature before the two-parameter one.
Where declaration merging is useful
The most common use of declaration merging is augmenting built-in types or library types without modifying their source code.
For example, you can add a custom property to the global Window interface. The Window type is declared by TypeScript's DOM library. You can extend it in your own code:
interface Window {
analytics: { track(path: string): void };
}
window.analytics.track("/home");The compiler merges your declaration with the built-in Window interface. Your analytics property is now available everywhere window is used. No need to modify any type definition file.
Another common pattern is extending Express request objects. Libraries like Passport add a user property to the request object. They do it through declaration merging on the Request interface.
Declaration merging vs type aliases
Type aliases cannot participate in declaration merging. If you try to declare the same type alias twice, the compiler reports a duplicate identifier error. This is one of the key reasons to choose interfaces over type aliases when you need a type that can be extended later.
| Interface | Type alias | |
|---|---|---|
| Redeclaring the same name | Merges into one type | Duplicate identifier error |
| Augmenting a library type | Supported | Not supported |
| Adding a conflicting member type | Compiler error | Not applicable |
type Window = { title: string };
type Window = { version: number };
// Error: Duplicate identifier 'Window'.If you are building a library or a plugin system where consumers may need to add properties to your types, use interfaces. The merging support lets consumers extend your types without forking them.
When not to use declaration merging
Declaration merging is powerful, but it can make code harder to understand. When an interface is declared in multiple places, a reader must find all declarations to see the full shape. Use merging deliberately, not as a convenience.
If you control all declarations of an interface and they are all in one file, there is no reason to split them. Declare the full interface in one place. Reserve merging for cases where the declarations genuinely live in different files or different packages.
For more on interfaces, see interfaces in TypeScript explained. For the type alias side of the story, see type aliases in TypeScript explained.
Common mistakes
Expecting declaration merging to work on type aliases. It does not. Only interfaces and namespaces support merging. If you need augmentable types, use interfaces.
Merging interfaces with conflicting non-function members. Two declarations of the same property must use the same type. If you need a different type, give the property a different name.
Splitting an interface across multiple files when all declarations are in one codebase you control. This makes the type harder to read. Keep related declarations together unless there is a specific reason to separate them, such as augmenting a library type from your application code.
Rune AI
Key Insights
- Declaration merging combines multiple declarations of the same interface into one.
- Only interfaces and namespaces support merging; type aliases do not.
- Non-function members with the same name must have identical types.
- Function members of the same name merge as overloads.
- The main use case is augmenting third-party or built-in types without modifying source code.
Frequently Asked Questions
Can type aliases participate in declaration merging?
What happens if two merged interfaces declare the same property with different types?
Where is declaration merging useful in practice?
Conclusion
Declaration merging is a unique TypeScript feature that lets you add properties to an interface by declaring it again. It powers patterns like augmenting global types, extending library types, and building plugin systems. Remember that only interfaces and namespaces support merging -- type aliases do not.
More in this topic
TypeScript Compiler Explained
The TypeScript compiler (tsc) turns .ts files into .js files while checking types. Learn what it does, how to use it, and how it fits your workflow.
Set Up ESLint for TypeScript
Install and configure ESLint with typescript-eslint to catch bugs and enforce consistent code style in your TypeScript project.
Pick Utility Type in TypeScript
Pick creates a new type by selecting only the properties you name from an existing type. Use it to build focused types for specific views, API responses, or function parameters.