Skip to content

Composite

Composite lets you build tree structures of part-whole hierarchies and treat individual objects and compositions of objects through the same interface, so clients do not need to know whether they hold a leaf or a whole branch.

Think of a file system: files have a size, and folders contain files and other folders, with their size being the sum of everything inside. If you model files and folders as unrelated types, every piece of client code that walks the tree has to ask “is this a file or a folder?” and branch accordingly. That conditional spreads everywhere and breaks the moment you add a third kind of node.

Composite removes the question. A common interface — say, a node with a size() method — is implemented by both leaves and containers. A leaf returns its own value. A container delegates to its children and combines their results, recursing as deep as the tree goes. The client calls size() on the root and gets the total, never caring about the shape underneath. Adding a node type means implementing the same interface, not editing every traversal.

classDiagram
  class Node {
    <<interface>>
    +size() int
    +name() string
  }
  class File {
    -bytes: int
    +size() int
  }
  class Folder {
    -children: Node[]
    +add(child)
    +size() int
  }
  Node <|.. File
  Node <|.. Folder
  Folder o--> Node : contains children
Leaves and containers share one interface; containers hold children
  • Component (Node) — the common interface for everything in the tree, declaring the operations clients call.
  • Leaf (File) — a node with no children; it implements the operation directly.
  • Composite (Folder) — a node that holds child components and implements the operation by delegating to and combining its children.
  • Client — works against the component interface and treats leaves and composites the same.

A file-system tree where both files and folders expose size(). A folder’s size is the sum of its children, computed recursively.

interface Node {
size(): number;
}
class File implements Node {
constructor(private readonly bytes: number) {}
size = () => this.bytes;
}
class Folder implements Node {
private children: Node[] = [];
add(child: Node): this {
this.children.push(child);
return this;
}
size = () => this.children.reduce((sum, c) => sum + c.size(), 0);
}
const root = new Folder()
.add(new File(100))
.add(new Folder().add(new File(20)).add(new File(30)));
console.log(root.size()); // 150
  • Pro: clients treat single objects and whole subtrees uniformly, eliminating type-checking conditionals.
  • Pro: adding new component types is open-ended — just implement the shared interface.
  • Pro: recursive operations like totals, rendering, or searches fall out naturally from the structure.
  • Con: a too-general component interface can force leaves to implement child operations that make no sense for them.
  • Con: the uniformity can hide cost: a single innocent call may recurse over a huge tree.
  • Decorator also relies on recursive composition, but it adds behaviour to a single wrapped object rather than aggregating many children.
  • Builder is handy for assembling a complex composite tree step by step.
What structure does the Composite pattern build?
How does a composite (container) implement the shared operation?
What benefit does treating leaves and composites uniformly give clients?
What is a downside of a very general component interface?