🧱 Structural

Composite Pattern

Composes objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly through a shared interface, making recursive tree structures easy to build and traverse.

Problem

In many systems, you deal with tree-like structures (menus, file systems, organizational charts). Using these structures only makes sense if the client code can treat both simple elements and complex containers in the same way. If the client needs type-checking to distinguish leaves from branches, the code becomes riddled with conditionals and hard to extend.

Solution

Define a common interface for both simple and complex elements. Simple elements (leaves) implement behavior directly. Complex elements (composites) hold a collection of child elements and delegate work by iterating over their children. The client uses the same interface to interact with all elements regardless of complexity.

Participants

RoleResponsibility
ComponentDeclares the common interface for objects in the composition
LeafRepresents leaf objects with no children; defines behavior for primitive objects
CompositeStores child components and implements child-related operations; delegates to children
ClientManipulates objects in the composition through the Component interface

TypeScript Example

TypeScript
interface FileSystemNode {
  name: string;
  getSize(): number;
  print(indent?: string): string;
}

class File implements FileSystemNode {
  constructor(public name: string, private size: number) {}

  getSize() { return this.size; }

  print(indent = "") {
    return `${indent}📄 ${this.name} (${this.size}KB)`;
  }
}

class Directory implements FileSystemNode {
  private children: FileSystemNode[] = [];

  constructor(public name: string) {}

  add(node: FileSystemNode) { this.children.push(node); }

  getSize(): number {
    return this.children.reduce((sum, c) => sum + c.getSize(), 0);
  }

  print(indent = ""): string {
    const lines = [`${indent}📁 ${this.name} (${this.getSize()}KB)`];
    for (const child of this.children) {
      lines.push(child.print(indent + "  "));
    }
    return lines.join("\n");
  }
}

const root = new Directory("src");
const components = new Directory("components");
components.add(new File("Button.tsx", 3));
components.add(new File("Modal.tsx", 5));
root.add(components);
root.add(new File("index.ts", 1));

console.log(root.print());
console.log("Total:", root.getSize(), "KB");

Real-World Example

File systems are the quintessential Composite example—files and folders share common operations (rename, copy, get size), but folders contain other files and folders. UI component trees work the same way: a panel can contain buttons, labels, and other panels. React's component model is fundamentally a Composite tree where components can contain other components.

When to Use

  • You want to represent part-whole hierarchies of objects
  • You want clients to treat all objects in the composite structure uniformly
  • You need to build recursive tree structures
  • You want to ignore the difference between compositions and individual objects

When Not to Use

  • The tree structure has many different types of nodes with radically different interfaces
  • You need to enforce constraints on which components can be children of which composites
  • The uniform interface forces you to add meaningless methods to leaf classes

Understanding the Composite Pattern

The Composite pattern is one of the most important structural design patterns in object-oriented software design. Originally cataloged by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," this pattern continues to be a cornerstone of modern software architecture.

Composes objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly through a shared interface, making recursive tree structures easy to build and traverse. The pattern involves the following key participants: Component, Leaf, Composite, Client. Each participant has a well-defined role, and the interactions between them create a flexible, maintainable structure that can evolve with changing requirements.

In TypeScript and modern JavaScript development, the Composite pattern is particularly valuable because the language's type system and interface support allow you to express the pattern with compile-time safety. TypeScript generics can make Composite implementations even more flexible and reusable while maintaining strong type checking throughout the pattern's structure.

Knowing when to apply the Composite pattern is as important as knowing how. Common use cases include: You want to represent part-whole hierarchies of objects; You want clients to treat all objects in the composite structure uniformly; You need to build recursive tree structures. However, overusing design patterns can lead to unnecessarily complex code. Always evaluate whether the pattern genuinely solves your problem or if a simpler approach would suffice.

The Composite pattern is related to several other design patterns: Decorator, Iterator, Visitor, Flyweight. Understanding these relationships helps you choose the right pattern for your specific situation and combine patterns effectively when building complex systems.

Related Patterns

More Structural Patterns

Explore All Design Patterns

Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.