Composite Pattern

Intent
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions uniformly. — Gang of Four (GoF)
Problem
- Product is a single item (e.g., a book).
- Box is a container that can hold many Products and other Boxes.
Solution
Structure
Plaintext
Component (interface)
├── Leaf (e.g., Product)
└── Composite (e.g., Box)
├── add()
├── remove()
└── operation()
TypeScript Example – Products and Boxes
Component Interface
Ts
interface Item {
getPrice(): number;
getName(): string;
}
Leaf – Product
Ts
class Product implements Item {
constructor(
private name: string,
private price: number,
) {}
getPrice(): number {
return this.price;
}
getName(): string {
return this.name;
}
}
Composite – Box
Ts
class Box implements Item {
private children: Item[] = [];
constructor(private name: string) {}
add(item: Item): void {
this.children.push(item);
}
remove(item: Item): void {
this.children = this.children.filter((i) => i !== item);
}
getPrice(): number {
return this.children.reduce((total, item) => total + item.getPrice(), 0);
}
getName(): string {
return this.name;
}
listContents(indent = 0): void {
console.log(`${" ".repeat(indent)}📦 ${this.name} - $${this.getPrice()}`);
for (const item of this.children) {
if (item instanceof Box) {
item.listContents(indent + 2);
} else {
console.log(
`${" ".repeat(indent + 2)}📄 ${item.getName()} - $${item.getPrice()}`,
);
}
}
}
}
Client Code
Ts
const book = new Product("Book", 12.99);
const pen = new Product("Pen", 1.99);
const notebook = new Product("Notebook", 4.99);
const smallBox = new Box("Stationery Box");
smallBox.add(pen);
smallBox.add(notebook);
const mainBox = new Box("Main Order");
mainBox.add(book);
mainBox.add(smallBox);
mainBox.listContents();
console.log(`Total Order Price: $${mainBox.getPrice().toFixed(2)}`);
Output
📦 Main Order - $19.97
📄 Book - $12.99
📦 Stationery Box - $6.98
📄 Pen - $1.99
📄 Notebook - $4.99
Total Order Price: $19.97
When to Use
- You need to work with tree structures (e.g., file systems, UI hierarchies, DOM, packaging).
- You want uniform treatment for individual and grouped objects.
- You need recursive behavior (like total price, rendering, movement, etc.).
Pros & Cons
Pros
- Simplifies client code by using polymorphism.
- Easily extendable: new leaf/composite types don’t break existing code.
- Recursive traversal logic is clean and centralized.
Cons
- Can introduce unnecessary generalization if leaf and composite behavior differ widely.
- Makes it harder to enforce type constraints (e.g., ensuring a Box only contains Products).