Composite Pattern
The Composite pattern lets you treat individual objects and groups of objects uniformly through a common interface. Use it when your data forms a tree — files and folders, UI nodes, organizational hierarchies — and clients should not care whether they're talking to a leaf or a branch.
Overview
The Composite pattern models part-whole hierarchies by giving leaves and containers the same interface. A folder behaves like a file: both have a size(), both can be rendered. The container's implementation simply delegates to its children and aggregates the results. The whole subtree becomes recursively manipulable without the client writing a single isFolder check.
When to use it
- Your domain is a tree (DOM, filesystem, menu structure, org chart).
- Clients should be able to operate on a single element or a whole subtree the same way.
- New leaf or composite types are added over time and you want them to slot in without touching callers.
Example
interface FileSystemNode {
size(): number;
}
class FileNode implements FileSystemNode {
constructor(private readonly bytes: number) {}
size() { return this.bytes; }
}
class FolderNode implements FileSystemNode {
private children: FileSystemNode[] = [];
add(node: FileSystemNode) { this.children.push(node); return this; }
size() { return this.children.reduce((sum, c) => sum + c.size(), 0); }
}
const root = new FolderNode()
.add(new FileNode(120))
.add(
new FolderNode()
.add(new FileNode(50))
.add(new FileNode(30))
);
console.log(root.size()); // 200Pros
- Clients work against one interface — no isLeaf branching.
- Adding a new node type doesn't ripple through the codebase.
- Recursive operations (size, render, search) become trivial to express.
Cons
- The shared interface can end up too generic, with leaf-only or composite-only methods.
- Hard to restrict the structure (e.g., "folder can only contain files of type X") at the type level.
- Excessive recursion on deep trees can hit performance or stack limits.