Menu
×
   ❮   
HTML CSS JAVASCRIPT SQL PYTHON JAVA PHP HOW TO W3.CSS C C++ C# BOOTSTRAP REACT MYSQL JQUERY EXCEL XML DJANGO NUMPY PANDAS NODEJS DSA TYPESCRIPT ANGULAR GIT POSTGRESQL MONGODB ASP AI R GO KOTLIN SASS VUE GEN AI SCIPY CYBERSECURITY DATA SCIENCE INTRO TO PROGRAMMING BASH RUST

TypeScript Declaration Merging


Understanding Declaration Merging

Declaration merging is a powerful TypeScript feature that allows you to combine multiple declarations with the same name into a single definition.

This enables you to build up complex types incrementally and extend existing types in a type-safe manner.


Key Benefits

  • Progressive Enhancement: Build types incrementally across multiple declarations
  • Extensibility: Add new members to existing types without modifying original definitions
  • Organization: Split large type definitions into logical groupings
  • Compatibility: Extend third-party type definitions when needed

Common Use Cases

  • Extending built-in types and third-party library types
  • Adding type information for JavaScript libraries
  • Organizing large interfaces across multiple files
  • Creating fluent APIs with method chaining
  • Implementing the module augmentation pattern

Interface Merging

Interfaces with the same name are automatically merged:

// First declaration
interface Person {
  name: string;
  age: number;
}

// Second declaration with the same name
interface Person {
  address: string;
  email: string;
}

// TypeScript merges them into:
// interface Person {
// name: string;
// age: number;
// address: string;
// email: string;
// }

const person: Person = {
  name: "John",
  age: 30,
  address: "123 Main St",
  email: "[email protected]"
};

console.log(person);
Try it Yourself »


Function Overloads with Merging

You can define multiple function declarations that later merge when implemented:

// Function overloads
function processValue(value: string): string;
function processValue(value: number): number;
function processValue(value: boolean): boolean;

// Implementation that handles all overloads
function processValue(value: string | number | boolean): string | number | boolean {
  if (typeof value === "string") {
    return value.toUpperCase();
  } else if (typeof value === "number") {
    return value * 2;
  } else {
    return !value;
  }
}

// Using the function with different types
console.log(processValue("hello")); // "HELLO"
console.log(processValue(10)); // 20
console.log(processValue(true)); // false
Try it Yourself »

Namespace Merging

Namespaces with the same name are merged:

namespace Validation {
  export interface StringValidator {
    isValid(s: string): boolean;
  }
}

namespace Validation {
  export interface NumberValidator {
    isValid(n: number): boolean;
  }

  export class ZipCodeValidator implements StringValidator {
    isValid(s: string): boolean {
      return s.length === 5 && /^\d+$/.test(s);
    }
  }
}

// After merging:
// namespace Validation {
// export interface StringValidator { isValid(s: string): boolean; }
// export interface NumberValidator { isValid(n: number): boolean; }
// export class ZipCodeValidator implements StringValidator { ... }
// }

// Using the merged namespace
const zipValidator = new Validation.ZipCodeValidator();

console.log(zipValidator.isValid("12345")); // true
console.log(zipValidator.isValid("1234")); // false
console.log(zipValidator.isValid("abcde")); // false
Try it Yourself »

Class and Interface Merging

A class declaration can merge with an interface of the same name:

// Interface declaration
interface Cart {
  calculateTotal(): number;
}

// Class declaration with same name
class Cart {
  items: { name: string; price: number }[] = [];

  addItem(name: string, price: number): void {
    this.items.push({ name, price });
  }

   // Must implement the interface method
   calculateTotal(): number {
    return this.items.reduce((sum, item) => sum + item.price, 0);
  }
}

// Using the merged class and interface
const cart = new Cart();
cart.addItem("Book", 15.99);
cart.addItem("Coffee Mug", 8.99);

console.log(`Total: $${cart.calculateTotal().toFixed(2)}`);

Enum Merging

Enum declarations with the same name are merged:

// First part of the enum
enum Direction {
  North,
  South
}

// Second part of the enum
enum Direction {
  East = 2,
  West = 3
}

// After merging:
// enum Direction {
// North = 0,
// South = 1,
// East = 2,
// West = 3
// }

console.log(Direction.North); // 0
console.log(Direction.South); // 1
console.log(Direction.East); // 2
console.log(Direction.West); // 3

// Can also access by value
console.log(Direction[0]); // "North"
console.log(Direction[2]); // "East"
Try it Yourself »

Module Augmentation

You can extend existing modules or libraries by declaring additional types and functionality:

// Original library definition
// Imagine this comes from a third-party library
declare namespace LibraryModule {
  export interface User {
    id: number;
    name: string;
  }
  export function getUser(id: number): User;
}

// Augmenting with additional functionality (your code)
declare namespace LibraryModule {
  // Add new interface
  export interface UserPreferences {
    theme: string;
    notifications: boolean;
  }

  // Add new property to existing interface
  export interface User {
    preferences?: UserPreferences;
  }

  // Add new function
  export function getUserPreferences(userId: number): UserPreferences;
}

// Using the augmented module
const user = LibraryModule.getUser(123);
console.log(user.preferences?.theme);

const prefs = LibraryModule.getUserPreferences(123);
console.log(prefs.notifications);
Try it Yourself »

Best Practices

There are some rules to consider when using declaration merging:

  • Order matters for function overloads: The implementation signature should be the most general
  • Non-function members must be compatible: If two interfaces declare a property with the same name, the types must be identical or compatible
  • Later interfaces take precedence: If conflicts exist in merged interfaces, the last declaration wins
  • Private and protected members: Classes can't merge if they have private or protected members with the same name but different types
  • Namespace exports: Only exported declarations are visible outside the namespace after merging

Performance Considerations

  • Compilation Time: Excessive declaration merging can increase compilation time
  • Type Checking: Complex merged types may impact IDE performance
  • Bundle Size: Declaration merging doesn't affect runtime performance or bundle size

Optimization Tips:

  • Keep merged interfaces focused and cohesive
  • Avoid deep nesting in merged types
  • Use type aliases for simple type combinations instead of merging



×

Contact Sales

If you want to use W3Schools services as an educational institution, team or enterprise, send us an e-mail:
[email protected]

Report Error

If you want to report an error, or if you want to make a suggestion, send us an e-mail:
[email protected]

W3Schools is optimized for learning and training. Examples might be simplified to improve reading and learning. Tutorials, references, and examples are constantly reviewed to avoid errors, but we cannot warrant full correctness of all content. While using W3Schools, you agree to have read and accepted our terms of use, cookie and privacy policy.

Copyright 1999-2025 by Refsnes Data. All Rights Reserved. W3Schools is Powered by W3.CSS.