Overview
The Composite pattern composes objects into tree structures and lets clients treat individual objects (leaves) and groups (composites) uniformly through the same interface. A folder and a file both have size(); a folder's size is the sum of its children's sizes, recursively.
Composite is the natural model for any part-whole hierarchy: file systems, UI component trees, organization charts, menus, product bundles, and expression trees. Client code does not need to distinguish leaves from composites, so recursive operations become simple.
A shipping box can contain products or smaller boxes that contain more products. To find the total weight, you ask each item for its weight; a box answers by asking its contents.
When to use it
- Part-whole hierarchies (trees).
- Clients should treat single items and groups the same way.
- Recursive operations: total price, render, count, search.
Where it shows up in interviews
Recognize it when: items that can contain other items.
- Design a file system
- Design an org chart
- Design a menu system
Recognize it when: products sold alone or as bundles.
- Design a pricing engine with bundles
- Design a restaurant combo menu
Where it is used in real software
Elements contain elements; rendering and event handling recurse through the tree.
Container extends Component so panels hold buttons and other panels.
Directories and files share operations like size, permissions, and delete.
Key terms
- Component
- Common interface for leaves and composites.
- Leaf
- Has no children (File).
- Composite
- Holds children and delegates to them (Folder).
- Recursive composition
- Composites can contain composites.
How it works, step by step
- 1Define the component interface
size(), render(), price().
- 2Implement leaves
Return their own value.
- 3Implement composites
Hold children, aggregate their results.
- 4Add child management
add/remove on composites.
- 5Guard against cycles
A folder cannot contain itself.
Folder size calculation
root/ { a.txt 10KB, photos/ { p1.jpg 200KB, p2.jpg 300KB }, docs/ { cv.pdf 50KB } }
| Node | size() | Computed as |
|---|---|---|
| a.txt | 10 | Leaf |
| photos/ | 500 | 200 + 300 |
| docs/ | 50 | 50 |
| root/ | 560 | 10 + 500 + 50 |
NOWNode: a.txt | size(): 10 | Computed as: Leaf
The client calls root.size() once; recursion handles any depth.
Implementation
interface FsNode { name: string; size(): number; print(indent?: string): string[];} class FileNode implements FsNode { constructor(readonly name: string, private bytes: number) {} size() { return this.bytes; } print(indent = "") { return [`${indent}${this.name} (${this.bytes})`]; }} class Folder implements FsNode { private children: FsNode[] = []; constructor(readonly name: string) {} add(node: FsNode) { if (node === this) throw new Error("A folder cannot contain itself"); this.children.push(node); return this; } size() { return this.children.reduce((s, c) => s + c.size(), 0); } print(indent = ""): string[] { return [`${indent}${this.name}/ (${this.size()})`, ...this.children.flatMap((c) => c.print(indent + " "))]; }} const root = new Folder("root") .add(new FileNode("a.txt", 10)) .add(new Folder("photos").add(new FileNode("p1.jpg", 200)).add(new FileNode("p2.jpg", 300)));root.size(); // 510Complexity and performance
Visits each once.
Beware very deep trees.
Trade-offs
Putting add() on the common interface makes leaves implement meaningless methods; putting it only on composites requires type checks when building.
Recomputing sizes on every call is simple; caching needs invalidation when children change.
Variants and related techniques
Add operations over the tree without changing node classes.
Traverse the tree depth-first or breadth-first.
Common mistakes
- Cycles in the tree.
Fix: Validate on add or track parents.
- Clients checking instanceof Folder everywhere.
Fix: Move behavior into the component interface.
Interview questions
How would you design a file system?
Composite: an abstract FsNode with name, size, and permissions; File as a leaf; Directory as a composite holding children; operations like size and search recurse through the tree.
Where do you put add/remove child methods?
Usually only on the composite for type safety; the transparent alternative puts them on the interface with leaves throwing, which is more uniform but violates LSP.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| File system with size and find | Easy | Recursion. |
| Org chart with total salary per manager | Medium | Aggregates. |