Structural · recursive part-whole
Composite
is a structural pattern that lets a single item and a group of items share one interface. Clients call the same method on the root whether that root is a leaf or a nested tree.
A concrete example: a folder’s size is not a number you typed by hand. It is the sum of the sizes of the files inside, and some of those “files” are folders too. You want one question — size() — without asking “are you a file?” at every level.
Explorer — one type, nested ownership
Client calls docs.size(). The folder sums children. No instanceof in the caller.
Core question
Can a leaf and a group share one type so clients recurse without instanceof?
Name the . Give leaves and groups that . Store children only on the group. Recurse inside the group. The client asks the root.
Three roles, one contract
Component
Node.size()
What every caller holds. Honest on a file and on a folder.
FileNode
Returns its own bytes. No list. No add().
Composite
Folder
List of Node. add() lives here. size() sums. This is the owner.
Java sketch: File and Folder share Node
Keep add on Folder. Clients that only have Node can still ask size(); they should not grow the tree. That choice respects Interface Segregation better than File.add throwing UnsupportedOperationException.
interface Node {
int size();
}
class FileNode implements Node {
private final int bytes;
FileNode(int bytes) { this.bytes = bytes; }
public int size() { return bytes; }
}
class Folder implements Node {
private final List<Node> children = new ArrayList<>();
void add(Node child) { children.add(child); }
public int size() {
int total = 0;
for (Node child : children) total += child.size();
return total;
}
}
// Node root = docs; int used = root.size();Same tree: rules that contain rules
A rule engine is Composite with a boolean verb. AmountAtLeast is a leaf. AllOf / AnyOf are groups. The checkout asks root.matches(order) and does not switch on rule kinds. See also Rule engine.
interface Evaluable {
boolean matches(Order order);
}
class AmountAtLeast implements Evaluable {
private final Money min;
public boolean matches(Order order) { return order.total().gte(min); }
}
class AllOf implements Evaluable {
private final List<Evaluable> rules;
public boolean matches(Order order) {
for (Evaluable rule : rules) {
if (!rule.matches(order)) return false;
}
return true;
}
}Org chart, UI widgets, in-memory FS
Employee and Manager both report headcount. A panel of widgets is a widget. An in-memory file system is this page in a case study: Directory owns children; File holds bytes; both are Node.
add() only on the group
GoF sometimes puts add on Component and makes leaves throw. That surprises callers and fights ISP. Prefer add on Folder. If a client has only Node, it can still ask size().
Not a list, not a visitor, not a wrap
| If you notice… | You probably want |
|---|---|
| A flat List<File> and no nested folders | A collection. Composite needs recursion. |
| Many unrelated operations over the same tree | Visitor on top of Composite |
| instanceof Folder in the client to walk | The pattern is failing in public. Push the walk into size(). |
| A wrapper that changes Stripe into Payable | Adapter, not a tree |
How to implement it (four steps)
- 01
Name the common verb
size, evaluate, draw, headcount — honest on a leaf and on a group.
- 02
Give leaves and groups that type
File and Folder implement Node. Rule and RuleGroup implement Evaluable.
- 03
Store children only on the group
Folder has List<Node>. File does not.
- 04
Recurse inside the group
The client calls root.size(). The tree does the walk.
Common mistakes
instanceof in the client
add() on every leaf
One file in two folders
How to explain this in an interview
“File and Folder both implement Node. size() on a folder sums children. The client never asks instanceof. add lives on Folder so leaves stay honest.”
- Draw docs/ with two files and one nested folder. Show which objects implement Node and where add lives.
- AND/OR rule groups: write matches() without a type switch in the caller.
Takeaway
Composite is a tree of one contract. Leaves answer the verb. Groups recurse. Clients never walk with instanceof. That is the whole pattern.