On this page
Description
Composite is the structural pattern for trees where a group should behave like a single item: a folder and a file both answer "what's your size?", a bundle and a product both answer "what's your price?". Leaves and groups share one interface, and a group answers by asking its children - so client code never checks which one it's holding. This skill covers the pattern, how the recursion works, and how to keep a tree a tree.
A file system has files and folders, and folders contain files and other folders. Computing the size of a folder without a plan looks like this:
Java
long size(Object node) {
if (node instanceof File f) return f.bytes();
long total = 0;
for (Object child : ((Folder) node).children()) {
total += size(child); // every operation needs this same type-check dance
}
return total;
}
Python
def size(node) -> int:
if isinstance(node, File):
return node.bytes
total = 0
for child in node.children:
total += size(child) # every operation needs this same type-check dance
return total
Every operation - size, count, search, print - repeats the same instanceof checks and casts. Add a new kind of node (a symbolic link, a zip archive) and you must update every one of them.
We'll go from simple to more technical:
- The pattern: one interface for leaves and composites.
- How the recursion works, step by step.
- Where you'll see composites: UI trees, menus, bundles, expression trees.
- Keeping trees safe - cycles, depth and where
add()belongs - and choosing among the structural patterns.
The pattern
Composite gives single items (leaves) and groups (composites) the same interface, so clients treat them uniformly. A composite implements each operation by delegating to its children.
Java
interface Node {
String name();
long size();
}
class FileNode implements Node {
private final String name;
private final long bytes;
FileNode(String name, long bytes) { this.name = name; this.bytes = bytes; }
public String name() { return name; }
public long size() { return bytes; }
}
class FolderNode implements Node {
private final String name;
private final List<Node> children = new ArrayList<>();
FolderNode(String name) { this.name = name; }
void add(Node child) { children.add(child); }
public String name() { return name; }
public long size() {
long total = 0;
for (Node child : children) total += child.size(); // no type checks - each child knows its own size
return total;
}
}
Python
from abc import ABC, abstractmethod
class Node(ABC):
def __init__(self, name: str):
self.name = name
@abstractmethod
def size(self) -> int: ...
class FileNode(Node):
def __init__(self, name: str, size_bytes: int):
super().__init__(name)
self._size_bytes = size_bytes
def size(self) -> int:
return self._size_bytes
class FolderNode(Node):
def __init__(self, name: str):
super().__init__(name)
self._children: list[Node] = []
def add(self, child: Node) -> None:
self._children.append(child)
def size(self) -> int:
return sum(child.size() for child in self._children) # no type checks
How it works, step by step
- Define one component interface for everything in the tree (
Node). - Leaves implement it directly (
FileNode.size()returns its bytes). - Composites hold a list of components and implement each operation by combining their children's results.
- Because children are also
Nodes, the recursion happens naturally - noinstanceofanywhere. - Clients call
size()on any node, file or folder, the same way.
Where you'll see it
- UI toolkits: a Swing
JPanel(a container) holds buttons and other panels; the browser DOM works the same way. - React/Angular component trees.
- Organization charts, menus with sub-menus, and product bundles that contain other bundles.
- Expression trees in compilers:
(1 + (2 * 3))is a tree of operations and numbers.
Choosing among the structural patterns
| If you need to... | Reach for | What it changes |
|---|---|---|
| Make an existing class fit the interface your code expects | Adapter | Changes the interface; the behavior stays the same. |
| Add behavior to an object without changing its class or interface | Decorator | Same interface, extra behavior, and wrappers can be stacked. |
| Give a complicated subsystem one simple front door | Facade | A new, simpler interface over many classes. |
| Treat a single item and a group of items the same way | Composite (this lesson) | A tree: groups contain items or other groups. |
| Control access to an object (lazy loading, caching, security, remote calls) | Proxy | Same interface, but it decides when and whether the real object is used. |
Common mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
instanceof / isinstance checks in every operation | every new node type edits every operation | one interface; composites delegate to children |
No cycle check in add() | size() recurses forever | reject the node itself and its ancestors |
| Naive recursion on very deep trees | stack overflow / RecursionError | an explicit stack or queue |
| The same node added under two parents | it's counted twice | one parent per node (moving re-parents it) |
add() on the shared interface without thinking | leaves can only fail at run time | choose transparency or safety deliberately |
Check yourself
Try to answer before opening each one - recalling beats re-reading.
1. How does a folder's size() work without any instanceof checks?
Show answer
It sums size() over its children. Files and folders share the Node interface, so each child answers for itself - a file returns its bytes, a folder recurses.
2. Transparency vs. safety: where would you put add(child), and what does each choice cost?
Show answer
On the composite only (safety): misuse is a compile error, but clients must know when they hold a composite. On the shared interface (transparency): every node is handled the same way, but a leaf's add can only fail at runtime.
3. How can add(child) refuse to create a cycle?
Show answer
Reject the child if it is the node itself or any of its ancestors - for example by walking up the parent links from the node before accepting the child.
Key takeaways
- Leaves and groups share one interface, so clients treat them the same way.
- A composite implements each operation by combining its children's results - recursion without
instanceof. - Protect the tree: prevent cycles, and beware of very deep recursion.
- Decide deliberately where child-management methods live (transparency vs. safety).
- Think file systems, UI containers, menus, bundles and expression trees.