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.

6 min read

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.

typescripttypescript
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.

typescripttypescript
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.

typescripttypescript
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:

typescripttypescript
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.

InterfaceType alias
Redeclaring the same nameMerges into one typeDuplicate identifier error
Augmenting a library typeSupportedNot supported
Adding a conflicting member typeCompiler errorNot applicable
typescripttypescript
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

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.
RunePowered by Rune AI

Frequently Asked Questions

Can type aliases participate in declaration merging?

No. Declaration merging only works for interfaces and namespaces. Type aliases are closed once declared. If you need a type that can be augmented later, use an interface.

What happens if two merged interfaces declare the same property with different types?

The compiler reports an error. All non-function members with the same name must have the same type. Function members of the same name are treated as overloads and merged into an ordered overload list.

Where is declaration merging useful in practice?

The most common use is augmenting built-in types or third-party library types without modifying the original source. It is also used to add custom properties to the global Window interface or to extend Express request objects with user session data.

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.