Before you start
Composite is one of the most-used patterns in real code — you just haven't been calling it by name.
Every tree-shaped structure where callers say "give me everything under this node" is Composite. File systems. UI trees. Menus. Org charts. JSON. HTML. Every ORM's related-object walks. Every AST evaluator.
The one-line version
Composite lets you treat individual objects and groups of them the same way, through a common interface.
The "no isinstance" signal
The clearest sign your code needs Composite:
if isinstance(child, File):
result += child.size
elif isinstance(child, Directory):
for c in child.children:
# ... recurse manually
That branching is the smell. Composite removes it — the
common interface says both types have .total_size(),
and Directory's version is just
sum(c.total_size() for c in children). No isinstance
in sight.
The test in this lesson AST-checks that Directory's
methods contain no isinstance or type() calls.
That's a strict version of "did you actually apply the
pattern?"
Where Composite shows up
Real production examples:
- File systems. (This lesson.)
- HTML DOM. Every element has children; text nodes
are leaves.
.render_to_string()recurses. - JSON parsing / serializing. Value (leaf), array
(container), object (container).
.to_json()walks the tree. - UI component trees. React, Vue, Angular — every component tree is Composite.
- Menu / navigation systems. Items and sub-menus
share
.render(). - Query builders.
AND(cond1, OR(cond2, cond3))— a tree of conditions with.evaluate()on each. - Test suites. Individual test + test class + test
module all support
.run().
Any time you write sum(c.total() for c in ...) and the
cs might be nested — you're using Composite.
Recursion caveat
Composite implementations usually recurse. In real production:
- Deep trees (thousands of nesting levels) hit Python's recursion limit. Switch to iterative walks using an explicit stack.
- Very wide trees are fine — depth is what matters, not total node count.
- Caching — for trees where computation is
expensive and the tree doesn't change often, cache
the result on each Directory node. Clear the cache on
.add().
Real file systems handle both. Composite is the pattern; the recursion strategy is an implementation detail.
Composite vs Decorator (they can look similar)
Both wrap. Different intent:
- Composite — wraps a COLLECTION of same-interface objects. Recursive tree shape. Caller sees the whole collection as one thing.
- Decorator — wraps ONE object with extra behavior. Linear chain shape.
If your wrapper holds a list of children, it's
Composite. If it holds ONE inner, it's Decorator.
The interview payoff
"How would you compute the total size of a directory
including all subdirectories?" — the naive answer is a
recursive function with isinstance checks. The senior
answer is: "make File and Directory both implement a
Node interface with a total_size method; the recursion
falls out of the pattern." That's the Composite insight.
The decision cheat-sheet
| Situation | Pattern |
|---|---|
| Tree structure, same operations on leaves and containers | Composite |
| Linear chain of same-interface wrappers | Decorator |
| Family of related objects that must be used together | Abstract Factory |
| Shape mismatch between two objects | Adapter |
| Simple entry point over a complex subsystem | Facade |