This TypeScript error occurs when the compiler cannot merge multiple interface declarations for the same interface name, typically due to conflicting property types, incompatible member signatures, or mismatched type parameters. The fix involves resolving type conflicts, ensuring consistent member signatures, or using module augmentation correctly.
The "Merge of interface X failed" error appears when TypeScript attempts to merge multiple interface declarations with the same name but encounters conflicts that prevent successful merging. TypeScript supports declaration merging for interfaces, allowing you to extend interface definitions across multiple declarations. However, merging fails when: 1. **Conflicting property types**: The same property is declared with incompatible types in different declarations 2. **Incompatible method signatures**: Methods with the same name have different parameter types or return types 3. **Mismatched type parameters**: Generic interfaces with different type parameter constraints 4. **Module boundary issues**: Attempting to merge interfaces across module boundaries incorrectly Declaration merging is a powerful TypeScript feature that enables extending built-in types, library types, and your own interfaces, but it requires consistency across all declarations. When merging fails, TypeScript cannot create a unified type definition, leading to this compilation error.
First, locate all declarations of the problematic interface. Search your codebase:
# Search for interface declarations
grep -r "interface X" src/ --include="*.ts" --include="*.tsx" --include="*.d.ts"
# Search in node_modules for library declarations
find node_modules -name "*.d.ts" -exec grep -l "interface X" {} \; 2>/dev/null
# Check global augmentations
grep -r "declare global" src/ --include="*.ts" --include="*.d.ts"Look for declarations in:
- Your source files (.ts, .tsx)
- Type definition files (.d.ts)
- Library type definitions in node_modules/@types
- Global augmentation files
Create a list of all locations where the interface is declared.
Examine each property in the conflicting declarations. The most common issue is incompatible property types:
// DECLARATION 1 (in file1.ts)
interface User {
id: string;
name: string;
age: number;
}
// DECLARATION 2 (in file2.ts) - CONFLICT: age is string vs number
interface User {
id: string;
name: string;
age: string; // CONFLICT: Different type than declaration 1
}
// SOLUTION: Make types compatible
interface User {
id: string;
name: string;
age: number | string; // Union type accepts both
}
// ALTERNATIVE: Use type intersection if appropriate
type User = {
id: string;
name: string;
age: number;
} & {
age?: string; // Optional string age
};Rules for successful merging:
- Properties must have identical types or compatible types (string | number merges with string)
- Optional properties (?) can merge with required properties (becomes optional)
- Readonly properties can merge with mutable properties (becomes readonly)
- Methods must have identical signatures (parameter types and return type)
Method signatures must be identical across all declarations:
// DECLARATION 1
interface ApiClient {
fetchData(url: string): Promise<Response>;
}
// DECLARATION 2 - CONFLICT: Different parameter type
interface ApiClient {
fetchData(url: URL): Promise<Response>; // Won't merge with string parameter
}
// SOLUTION 1: Make signatures identical
interface ApiClient {
fetchData(url: string | URL): Promise<Response>; // Accept both
}
// SOLUTION 2: Use function overloads in a single declaration
interface ApiClient {
fetchData(url: string): Promise<Response>;
fetchData(url: URL): Promise<Response>;
}
// SOLUTION 3: Separate the interfaces if truly different
interface StringApiClient {
fetchData(url: string): Promise<Response>;
}
interface UrlApiClient {
fetchData(url: URL): Promise<Response>;
}For overloaded methods, all declarations must be in the same interface block or properly merged.
Generic interfaces must have identical type parameter constraints:
// DECLARATION 1
interface Container<T extends string> {
value: T;
}
// DECLARATION 2 - CONFLICT: Different constraint
interface Container<T extends number> { // Won't merge with string constraint
value: T;
}
// SOLUTION: Use identical constraints
interface Container<T extends string | number> {
value: T;
}
// OR separate into different interfaces
interface StringContainer<T extends string> {
value: T;
}
interface NumberContainer<T extends number> {
value: T;
}
// For complex cases, use conditional types
interface Container<T> {
value: T extends string ? T : T extends number ? T : never;
}If you need different behavior for different types, consider using:
- Separate interface names
- Type aliases with conditional types
- Discriminated unions instead of merging
When extending library interfaces, use correct augmentation syntax:
// WRONG: Trying to merge incorrectly
interface SomeLibraryInterface {
newMethod(): void; // May conflict with library's declaration
}
// CORRECT: Use module augmentation
declare module "some-library" {
interface SomeLibraryInterface {
newMethod(): void; // Properly augments the library's interface
}
}
// For global types (Window, Document, etc.):
declare global {
interface Window {
myCustomProperty: string;
}
interface Document {
newMethod(): void;
}
}
// For ambient declarations in .d.ts files:
// types/custom.d.ts
declare interface MyGlobalInterface {
extendedProperty: boolean;
}
// In your source code, this will merge with other declarations
interface MyGlobalInterface {
anotherProperty: string;
}Key rules for module augmentation:
- Use declare module "module-name" for external libraries
- Use declare global for global scope augmentations
- Augmentations must be in .d.ts files or files with declare keyword
- Don't re-export the augmented module unless necessary
Third-party type definitions can conflict with your code:
# Check for conflicting @types packages
npm list @types/some-library
# If multiple versions exist, resolve them
npm uninstall @types/some-library-old
npm install @types/some-library@latest
# Or use type patching with patch-package
npm install patch-package --save-devCommon solutions:
1. Update type definitions:
npm update @types/node @types/react @types/express2. Use type assertion to avoid conflicts:
// When library types conflict with yours
import { SomeType } from "some-library";
// Use your own type definition
interface MyType extends Omit<SomeType, "conflictingProperty"> {
conflictingProperty: string; // Your preferred type
}
// Or use type merging with careful constraints
type MergedType = SomeType & {
conflictingProperty?: string; // Make optional to avoid conflict
};3. Create custom type definitions:
// types/custom.d.ts
declare module "problematic-library" {
export interface FixedInterface {
// Your corrected type definitions
}
}4. Use skipLibCheck as temporary workaround (not recommended for production):
{
"compilerOptions": {
"skipLibCheck": true
}
}### Understanding Declaration Merging Rules
TypeScript allows merging of:
1. Interfaces: Multiple interface declarations with the same name merge
2. Namespaces: Multiple namespace declarations merge
3. Functions: Function declarations can merge (function overloads)
4. Enums: Enum members merge across declarations
5. Classes and interfaces: Class can merge with interface (adds static members)
Interface merging specific rules:
- Properties with same name must have identical or compatible types
- String literal types merge with string type
- Numeric literal types merge with number type
- Optional properties (?) merge with required properties (result is optional)
- Readonly properties merge with mutable properties (result is readonly)
- Method signatures must be identical for successful merging
### Module Augmentation vs Declaration Merging
Module Augmentation: Extending types from external modules
declare module "external-lib" {
interface ExternalInterface {
customMethod(): void;
}
}Global Augmentation: Adding to global scope
declare global {
interface Array<T> {
customMethod(): void;
}
}Ambient Declaration Merging: Multiple .d.ts files in your project
// types/file1.d.ts
interface SharedType {
prop1: string;
}
// types/file2.d.ts
interface SharedType {
prop2: number; // Merges with file1 declaration
}### Debugging Merge Conflicts
Use TypeScript's compiler to trace merging:
# Show detailed type information
npx tsc --diagnostics
# Get specific error details
npx tsc --explainFiles # Shows which files contribute to types
# Check declaration emit
npx tsc --declaration --emitDeclarationOnly### Common Pitfalls
1. Circular Dependencies:
- File A declares interface X extends Y
- File B declares interface Y extends X
- Solution: Break circular dependency or use type aliases
2. Conditional Types in Merging:
- Conditional types (T extends U ? A : B) don't merge well
- Use intersection types instead: TypeA & TypeB
3. Mapped Types:
- { [K in keyof T]: ... } may not merge as expected
- Consider using interface extensions instead
4. Template Literal Types:
- Complex template types (
X = `prefix-${string}`) have limited merge compatibility
- Use simpler union types when merging is needed
### When to Avoid Merging
Sometimes it's better to avoid merging entirely:
1. Use Type Aliases: type User = BaseUser & AdditionalProps
2. Use Composition: class User implements BaseUser, HasAdditionalProps
3. Use Namespaces: Group related interfaces under a namespace
4. Use Discriminated Unions: For fundamentally different types
// Instead of merging conflicting interfaces
interface Admin { role: "admin"; adminOnly: boolean; }
interface User { role: "user"; userOnly: string; }
// Use discriminated union
type Person = Admin | User;### TypeScript Configuration for Merging
Ensure your tsconfig.json supports declaration merging:
{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"composite": true, // For project references
"moduleResolution": "node",
"esModuleInterop": true
},
"include": ["src/**/*", "types/**/*"]
}For large projects with many declarations, consider:
- Using project references to isolate type domains
- Creating a central types directory with clear ownership
- Using references in tsconfig.json to manage dependency order
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript