Design Patterns: Structural · Structural Patterns

Composite Pattern

Letting a single object and a nested tree of them be addressed through the exact same interface.

  • 5 min read
  • 11 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The pattern
  3. How it works, step by step
  4. Where you'll see it
  5. Choosing among the structural patterns
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

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:

  1. The pattern: one interface for leaves and composites.
  2. How the recursion works, step by step.
  3. Where you'll see composites: UI trees, menus, bundles, expression trees.
  4. 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

  1. Define one component interface for everything in the tree (Node).
  2. Leaves implement it directly (FileNode.size() returns its bytes).
  3. Composites hold a list of components and implement each operation by combining their children's results.
  4. Because children are also Nodes, the recursion happens naturally - no instanceof anywhere.
  5. 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 forWhat it changes
Make an existing class fit the interface your code expectsAdapterChanges the interface; the behavior stays the same.
Add behavior to an object without changing its class or interfaceDecoratorSame interface, extra behavior, and wrappers can be stacked.
Give a complicated subsystem one simple front doorFacadeA new, simpler interface over many classes.
Treat a single item and a group of items the same wayComposite (this lesson)A tree: groups contain items or other groups.
Control access to an object (lazy loading, caching, security, remote calls)ProxySame interface, but it decides when and whether the real object is used.

Common mistakes

MistakeWhat happensDo this instead
instanceof / isinstance checks in every operationevery new node type edits every operationone interface; composites delegate to children
No cycle check in add()size() recurses foreverreject the node itself and its ancestors
Naive recursion on very deep treesstack overflow / RecursionErroran explicit stack or queue
The same node added under two parentsit's counted twiceone parent per node (moving re-parents it)
add() on the shared interface without thinkingleaves can only fail at run timechoose 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.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Pick the tool

You need to...Reach for
Treat items and groups alikeComposite
Many operations over stable node typesVisitor, or sealed types + pattern matching
Walk a tree you don't controlan explicit stack or queue
Fast repeated aggregatescached values invalidated up the parent chain
Store the tree relationallyadjacency list + recursive CTE; closure table for heavy subtree reads

Snippets worth knowing by heart

Java

interface Node { long size(); }
record FileNode(String name, long bytes) implements Node { public long size() { return bytes; } }
final class FolderNode implements Node {
    private final List<Node> children = new ArrayList<>();
    void add(Node child) { children.add(child); }
    public long size() { return children.stream().mapToLong(Node::size).sum(); }
}

Python

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)

The traps

  • Type checks in every operation.
  • No cycle check - infinite recursion.
  • Deep recursion on data you don't control.
  • A node with two parents - double counting.
  • Unplanned add() placement - transparency vs safety.

Staff-level points, one line each

  • Transparency (shared add, runtime failure) vs safety (composite-only add, compile-time) - lean safe.
  • A 200,000-level chain overflowed Java's stack; 100,000 levels hit Python's RecursionError; an explicit stack handled both.
  • A child added twice made a folder report 200 bytes instead of 100 - one parent per node.
  • Cached sizes need invalidation up the parent chain, and all changes through methods (a direct field edit left the root at 16, not 26).
  • Changing children mid-walk: Java CME, a Python list silently grows, a dict raises - iterate a snapshot.
  • Operations multiplying? Visitor or sealed types with exhaustive switch/match.
  • Trees in SQL: adjacency list, materialized path, closure table, nested sets.

Say it in an interview

  • "Folders and files share Node, so size() works on either without type checks."
  • "add() lives only on folders, so adding to a file doesn't compile."
  • "The imported tree can be very deep, so traversal uses an explicit stack."
  • "Folder sizes are cached and invalidated up the parent chain on every change."

Practice

This skill comes with 11 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Composite Pattern free

Sizes, Counts and a Tree View With Composite

MediumProCoding problemJava · Python

Files and directories both implement FsNode, so client code never needs to check which one it holds. Building the tree is already done - you implement the three operations on both node types:

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Evaluate and Print an Expression Tree

MediumProCoding problemJava · Python

An arithmetic expression is a tree: numbers are leaves, and each operator is a composite whose two children are expressions themselves. (1 + (2 * 3)) is a + node with the leaf 1 on the left and a * node on the right.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Composite Pattern free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime