Free Interactive Course · Design Patterns

Composite Design Pattern

Treat a whole tree and a single object exactly the same way — so the caller stops asking which one it has.

Structural Patternsmediuma.k.a. Object Tree
ShareXLinkedIn
In one sentence

Compose objects into tree structures to represent part-whole hierarchies, letting clients treat individual objects and compositions of objects uniformly.

01

The problem Composite solves

The problem

You're building the monthly cloud bill. A service costs money — an EC2 fleet, an S3 bucket, an RDS instance. A project is a bag of services. An account is a bag of projects. And a sub-account is a bag of accounts, because someone acquired a company.

So totalFor(thing) starts as one if, and grows: is it a service? Return its cost. Is it a project? Loop its services. An account? Loop its projects, and inside each, loop its services. Every function that touches the tree — cost, tagging, the CSV export, the "which team owns this" report — grows its own copy of that same nested walk.

Then the sub-account arrives and every one of those functions is wrong by one level of nesting. The bug isn't that the code is complicated. It's that the caller is being asked to know the shape of the tree in order to ask a simple question.

02

How the Composite pattern works

Make the container and the thing it contains implement the same interface, and let recursion do the rest:

  1. Define a component interface with the operation the client actually wants: monthlyCost().
  2. A leaf implements it directly — a service returns its own cost and stops.
  3. A composite implements the very same interface by asking each of its children and combining the answers. It never checks what kind of child it has.
  4. The client holds a Component and calls one method. Whether that's one bucket or an entire acquired company is not its problem.
The design decision nobody warns you about. Do add() and remove() belong on the component interface or only on the composite? GoF calls this the trade-off between transparency and safety. Put them on the component and every leaf must implement add() by throwing — uniform, but a lie the compiler can't catch. Put them only on the composite and the client must test the type before it can build anything — safe, but the uniformity you came for is gone at exactly the moment you need it. Most modern code chooses safety: read operations are uniform, child management lives on the composite, and the tree is built by code that knows what it's building.
«interface» Component+ monthlyCost() : MoneyService (leaf)returns its own costResourceGroup- children : List<Component>holds manyaccount.monthlyCost() → recurses to every leaf, whatever the depththat loop back to Component is the pattern — a container that is also a containable
Participants. The Component declares the operation. The Leaf does the work and has no children. The Composite implements the same operation by delegating to its children and combining their answers — and because its children are typed as Component, one of them can be another composite. That single loop-back edge in the diagram is the whole pattern.
03

See it: one call, any depth

Add things to the account and watch the total. Some of what you add is a single service; one of them is an entire sub-account with its own tree inside. The call at the top never changes and never learns the difference.

▶ Try it — build the tree, ask once

An interactive cost tree: add EC2, S3, RDS, Lambda and an entire acquired sub-account to a production account, and a single call to account.monthlyCost() returns the total however deep the tree goes, because leaves and composites implement the same interface.

The last chip is the one that matters. northwind is not a service — it's a whole tree with nine leaves of its own. The account added it exactly the way it added the S3 bucket, and the total call didn't gain a branch, a cast or a depth parameter. That is what "treat individual objects and compositions uniformly" buys you, stated in one number.
04

Composite pattern code examples

A leaf and a container implementing one interface — and the recursion that follows for free.

public interface Component {
    Money monthlyCost();
    String name();
}

/** Leaf: knows its own cost, has no children. */
public record Service(String name, Money monthlyCost) implements Component {}

/** Composite: same interface, answers by asking its children. */
public final class ResourceGroup implements Component {

    private final String name;
    private final List<Component> children = new ArrayList<>();

    public ResourceGroup(String name) { this.name = name; }

    // add() lives HERE, not on Component — the "safe" side of GoF's trade-off.
    public ResourceGroup add(Component child) {
        children.add(child);
        return this;
    }

    @Override public String name() { return name; }

    @Override public Money monthlyCost() {
        return children.stream()
                       .map(Component::monthlyCost)   // never asks what kind of child
                       .reduce(Money.ZERO, Money::plus);
    }
}

// Build any shape; ask one question:
Component account = new ResourceGroup("production")
        .add(new ResourceGroup("analytics")
                .add(new Service("ec2", Money.gbp(320)))
                .add(new Service("s3", Money.gbp(85))))
        .add(northwindSubAccount);        // an entire tree, added like a leaf

Money total = account.monthlyCost();      // depth is not the caller's problem
Read across the tabs: C++ has to answer a question the others get for free — who owns the children — and the answer (unique_ptr down, weak_ptr back up) is what stops a tree leaking. Python's tab carries the recursion-depth warning that bites real filesystem walks. And TypeScript's shows the honest alternative: for a closed set of node types, a discriminated union with an exhaustive switch gives you compiler-checked completeness that the class-based version can't.
05

How to implement Composite

  1. Write the component interface around the question the client asks — not around the tree's shape.
  2. Implement the leaf first. If a leaf can't answer the question sensibly, the interface is wrong.
  3. Implement the composite by iterating children and combining. It must never test what kind of child it holds.
  4. Decide the safety/transparency trade-off deliberately: put add/remove on the composite (safe, recommended) or on the component (uniform, but leaves must throw).
  5. Decide ownership and lifetime — who destroys a child, and whether a child may appear in two trees.
  6. Guard against cycles and unbounded depth: a parent back-reference must be weak, and a walk over user-shaped data should use an explicit stack rather than recursion.
  7. Cache aggregate results only if you can invalidate them; a stale total on a tree that just changed is worse than a slow one.
06

When to use Composite — and when not to

Use it when your data genuinely is a part-whole hierarchy — files and folders, groups of shapes, org charts, nested UI, bills of materials, resource trees — and when clients should be able to ignore whether they are holding one thing or a thousand.

Where it goes wrong

An interface watered down to fit both. Forcing a leaf and a container to share one interface can make it so general it says nothing: execute(), getChildren() returning empty, a size() that means different things. If the shared operations feel invented rather than obvious, the two things may simply not be the same kind of thing.

Silent performance cliffs. account.monthlyCost() looks like a field read and may be ten thousand recursive calls, each hitting a database. The uniformity that makes the pattern pleasant is exactly what hides the cost. Measure the leaf count, and consider caching with explicit invalidation.

Cycles. Nothing in the structure prevents a group containing an ancestor of itself, and the first symptom is a stack overflow in production. Validate on insert if the tree is built from user data, and keep parent references weak.

Deep recursion. User-shaped trees have no depth limit. Python raises RecursionError around a thousand frames; other languages simply crash. An explicit stack is unglamorous and doesn't fall over.

The add/remove lie. A Leaf.add() that throws at runtime is a compile-time guarantee you gave away. If you take the transparent option, know that you chose it.

You want to…UseBecause
Treat one object and a tree of objects identicallyCompositeLeaf and container share one interface, and recursion does the rest.
Add behaviour to one object without changing its interfaceDecoratorA Decorator is a composite with exactly one child that adds something.
Add a new operation over an existing tree without editing the nodesVisitorVisitor is the usual partner: Composite defines the tree, Visitor keeps operations off it.
Walk the tree without exposing its structureIteratorGives clients a flat sequence over the nested shape.
Share the identical parts of a huge number of nodesFlyweightOften combined when the tree has millions of leaves.
07

Quick check

🧠 Quick check
Should add(child) and remove(child) go on the Component interface, or only on the composite?

In the wild

JavaScriptThe DOM — an Element contains nodes and is a node, so querySelectorAll and textContent work identically on one element or a whole document.
Javajava.awt.Container and Swing's JComponent: a panel is a component that holds components, which is why layout code never asks how deep it is.
Goerrors.Join — one error value wrapping many, so errors.Is walks the tree and callers handle "one failure" and "nine" the same way.
Pythonxml.etree.ElementTree.Element — an element is both a node and a container of elements, and iter() flattens any depth.
C#The WPF visual tree, and Control.Controls in WinForms — containers that are themselves controls.
C++Qt's QWidget parent/child tree, which also handles ownership: destroying a parent destroys its children.

Frequently asked questions

What is the Composite pattern used for?
Any part-whole hierarchy where the client shouldn't have to care which it is holding: files and folders, UI containers and widgets, groups of shapes in a drawing tool, org charts, bills of materials, nested resource or cost trees. The test is whether the sentence "a group of X behaves like a single X" is true for the operations you care about. If it is, Composite removes an entire family of type checks from the calling code.
What is the difference between Composite and Decorator?
Both put an object behind an interface it also implements, and both recurse. A Composite holds many children and exists to aggregate them — its answer combines theirs. A Decorator holds exactly one and exists to add behaviour around it. You can think of Decorator as a degenerate Composite with a single child, which is why the two chapters sit next to each other in the book.
Should add() and remove() be on the Component interface?
This is GoF's transparency-versus-safety trade-off, and there is no universally right answer. On Component: everything looks uniform, but leaves must throw at runtime for an operation the type system said was fine. On the composite only: the compiler protects you, but client code has to know it holds a composite before it can build anything. Most modern code chooses safety, because the uniformity you actually wanted applies to reading the tree — the code that builds it always knows what it's building.
How do I avoid infinite recursion in a composite tree?
Three habits. Validate on insert if the tree is built from user or database data — walk up the parents and refuse to add an ancestor. Keep parent back-references weak (weak_ptr, a weak reference, or just an id), so a cycle can't also leak memory. And for trees of unbounded depth, replace recursion with an explicit stack: it's less elegant and it doesn't blow the call stack on the one customer whose folder nesting is four thousand deep.
Is the DOM a Composite?
Yes, and it's the clearest example most developers use daily. Element extends Node and contains Nodes, so a single element and an entire document expose the same API — appendChild, textContent, querySelectorAll all work regardless of depth. It also demonstrates the pattern's main pitfall: textContent on a large subtree looks like a property read and is a full recursive walk.

Finish the handbook, earn the certificate

All 23 Gang-of-Four patterns, each with a simulator you can click and code in seven languages. Free, no signup.

See all 23 patterns →
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.