bugl
bugl
HomeLearnPatternsPathsSearch
HomeLearnPatternsPathsSearch

Loading lesson path

Learn/TypeScript/TypeScript Core
TypeScript•TypeScript Core

TypeScript Declaration Merging

Flash cards

Review the key moves

1/4
Core idea

What is the main idea behind TypeScript Declaration Merging?

Lesson checks

Practice each idea before moving on

Short Mimo-style checks built from this lesson's code, terms, and sequence.

1Quick choice

Which statement best captures the main point of this lesson?

2Fill blank

Complete the missing token from the example code.

// ___ declaration interface Person { name: string; age: number;
3Order

Put the learning moves in the order that makes the concept easiest to apply.

Class and Interface Merging
Function Overloads with Merging
Understanding 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:

Example

// 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: "john@example.com"
};
console.log(person);

Function Overloads with Merging

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

Example

// 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

Namespace Merging

Namespaces with the same name are merged:

Example

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

Class and Interface Merging

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

Example

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

Example

// 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"

Module Augmentation

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

Example

// 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);

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
  • Keep merged interfaces focused and cohesive
  • Avoid deep nesting in merged types
  • Use type aliases for simple type combinations instead of merging

Previous

TypeScript Index Signatures

Next

TypeScript Async Programming