Composite Pattern
Treat individual objects and tree structures uniformly. An intermediate structural pattern — the foundation of file systems, UI component trees, and any recursive part-whole hierarchy.
Why Composite Exists
You are building a file system. A directory can contain files and other directories. You need to calculate the total size: a file returns its own size; a directory returns the sum of all its contents (recursively). The naive approach: check the type at every level with instanceof, handle files one way and directories another, and pray you never add a third type (symlink, archive, virtual mount).
What goes wrong:
- Type-checking everywhere — every operation (
getSize(),search(),delete(),print()) must checkinstanceofat every node - No uniform treatment — the client must handle files and directories with completely different code paths, even though both represent “something in the file system”
- Open/Closed violation — adding a new node type (symlink, archive) requires editing every operation
- Recursive logic exposed — the client manages the recursion manually; the tree structure leaks into every method that walks it
This is the problem Composite solves — define a common interface (FileSystemNode) that both files and directories implement. A file returns its own size; a directory sums its children’s sizes. The client calls node.getSize() uniformly on any node — it doesn’t know or care whether it’s a leaf or a branch. The recursion is hidden inside the composite.
What Is Composite?
Composite is a structural pattern that lets you compose objects into tree structures and then treat individual objects and compositions of objects uniformly. The key insight: both a leaf (file) and a branch (directory) implement the same interface. The client calls one method (getSize()) and doesn’t know whether it hit a single object or an entire subtree.
— Gamma, Helm, Johnson, Vlissides (1994)
execute(order). A soldier carries it out directly (leaf); a division delegates to its children (composite). Same interface, uniform treatment, recursive structure.
Key insight: Composite is about recursive composition with uniform interface. The leaf does the work directly; the composite delegates to its children. The client never checks types; it just calls the method and lets polymorphism handle the rest. This is why file systems, UI component trees, menu structures, and org charts all use Composite — they’re all part-whole hierarchies where “do X” on a group means “do X on each member.”
instanceof, no type checksFile.getSize() returns its own size)List<Component>) and delegates the operation to each childDirectory.getSize() sums children.stream().mapToLong(Component::getSize).sum()- Both use recursive composition (wrapping objects that share an interface).
- The difference: Composite is about tree structures with children — one parent, many children, same operation on all.
- Decorator is about stacking behaviours — one wrapper around one object, adding features.
- Composite = many children (fan-out). Decorator = one wrapped object (chain).
Participants & Structure
| Participant | Role | In the Analogy |
|---|---|---|
| Component | The common interface (or abstract class) declaring operations shared by leaves and composites (e.g. getSize(), print()). The client programs to this interface exclusively. | The “unit” abstraction — anything that can receive the “advance” command: soldier or division. |
| Leaf | Implements the Component interface directly. Has no children. The base case of the recursion (e.g. File returns its own size). | An individual soldier — receives the order and acts on it directly. |
| Composite | Implements the Component interface and holds a collection of children (List<Component>). Its operation delegates to each child (e.g. Directory.getSize() sums children’s sizes). Also provides add() / remove() for managing children. | A platoon/brigade/division — passes the order down to all sub-units. |
| Client | Works with the Component interface only. Doesn’t know (or care) whether it’s talking to a Leaf or Composite. Calls component.getSize() on any node. | The general — issues one command; the hierarchy handles the rest. |
- Notice how
Directoryholds aList<FileSystemNode>— the same interface it implements. - This self-referential relationship allows arbitrary nesting.
- A Directory’s children can be Files (leaves) or other Directories (composites containing more nodes).
- The client only ever sees
FileSystemNode.
The Pattern In Motion
Scenario: A project folder containing source files and sub-directories. Calculate the total size with a single call โ the recursion is hidden inside the composite.
File("App.java", 1200), File("README.md", 400), File("Utils.java", 800)FileSystemNode.getSize() by returning their stored size value.Directory("src"), Directory("project")List<FileSystemNode> and implement getSize() by summing children.src.add(App), src.add(Utils), project.add(src), project.add(README)project/ contains src/ (with 2 files) + README.md. Nesting is arbitrary — same add() for files and directories.project.getSize()project asks each child for its size. README (leaf) returns 400. src (composite) asks its children: App returns 1200, Utils returns 800 โ src returns 2000. Total: 2400. Client never managed the recursion.FileSystemNodegetSize() on the tree still works — polymorphism handles it.- Notice how the client calls
project.getSize()and gets the recursive total. - It never asks “are you a file or directory?” — it just calls the interface method.
- The composite pattern hides the recursion inside
Directory.getSize(), which iterates its children list and sums their sizes. - Each child either returns its own size (leaf) or recursively sums its children (composite).
- One method call, uniform treatment, arbitrary depth.
You Already Use This
Composite is everywhere in Java — any tree structure where you treat leaves and branches the same way uses this pattern.
java.awt.Container — the textbook Composite. A Container (JPanel, JFrame) holds child Components (JButton, JLabel). Both Container and Component share the same interface: paint(), setVisible(), getPreferredSize(). Calling panel.paint(g) recursively paints all children. A JPanel inside a JFrame inside another JPanel — arbitrary nesting, uniform treatment. java.io.File (legacy) / java.nio.file.Path — a File represents both files (leaves) and directories (composites). listFiles() returns children for directories. The newer Files.walk(path) traverses the tree uniformly. While not a pure GoF Composite (no shared getSize() method), the part-whole hierarchy structure is identical. javax.swing.JComponent / javax.faces.component.UIComponent — JSF’s component tree is pure Composite. Every UIComponent can have children (getChildren()) or be a leaf. Rendering calls encodeAll() which recursively renders the entire subtree. JSF pages are component trees processed uniformly. - When you call
frame.repaint(), it propagates down through every nested panel, button, and label. - Each component either paints itself (leaf) or paints itself then asks its children to paint (composite).
- This is why you can nest JPanels inside JPanels inside JFrames to any depth — it’s Composite all the way down.
Build It Once
Domain: File System. A FileSystemNode interface, a File leaf, and a Directory composite with getSize() and print() operations.
record for File?: - In the core snippet above,
Fileis a Javarecord— it’s immutable, has no children, and its state is just name + size. - Records are perfect for Composite leaves because leaves are data objects with no structure management.
- Composites (
Directory) are regular classes because they manage mutable children lists.
Common Mistakes
add()/remove() on the Component interface: The GoF shows add(child) on the Component interface for “transparency” — so the client doesn’t need to distinguish leaves from composites. But this means calling file.add(child) on a leaf must throw an exception or silently fail. Modern consensus: put add()/remove() only on the Composite (Directory). This sacrifices some transparency for type safety — the compiler tells you a File can’t have children.
- If you accidentally add a directory as its own child (
dir.add(dir)) or create a circular reference,getSize()will recurse infinitely โStackOverflowError. - Fix: Check for cycles in
add():if (node == this || isAncestor(node)) throw new IllegalArgumentException("Cycle detected").
- If
Directory.getChildren()returns the internalList, external code can bypassadd()/remove()and mutate the tree without validation. - Fix: Return
Collections.unmodifiableList(children)or an iterator — never the raw mutable list.
- Both use recursive composition with a shared interface.
- The difference: Composite is a tree (one parent, many children) where an operation on a group means “do it on each child.” Decorator is a chain (one wrapper around one object) where each layer adds behaviour.
- If your structure has one child per node, that’s Decorator.
- If it has many children per node, that’s Composite.
When To Use Composite
- You have a part-whole hierarchy — objects that contain other objects of the same type: file systems, org charts, UI component trees, menu structures
- You want clients to treat individual objects and groups uniformly — the same operation (size, render, execute) applies to both leaves and branches
- You need recursive structures where nesting depth is arbitrary or unknown at compile time
- You want to add new node types (e.g. symlink, archive) without modifying existing code — just implement the Component interface
- Operations on a group mean “do it on each member” — size of a directory = sum of children’s sizes, rendering a panel = render each child widget
- Your structure is flat — if there’s no nesting (just a list of items), a simple collection is enough; Composite adds unnecessary abstraction
- Leaf and Composite operations are fundamentally different — if the behaviour of a group differs drastically from an individual, forcing them into the same interface leads to awkward no-op methods
- You need to stack behaviours on a single object (one wrapper, one child) — that’s Decorator, not Composite
- You need different traversal strategies — Composite hard-codes the traversal inside the composite node; consider Iterator or Visitor if you need multiple traversal algorithms
- Your hierarchy is fixed and shallow (e.g. always exactly 2 levels) — the overhead of the pattern isn’t justified
| Pattern | Purpose | Structure | When to pick it |
|---|---|---|---|
| Composite ← this | Treat trees uniformly | One parent, many children | Part-whole hierarchy, recursive operations |
| Decorator | Add behaviour to same interface | One wrapper, one wrapped object | Stack features without subclassing |
| Iterator | Traverse a collection | External cursor over elements | Need multiple traversal strategies on the same tree |
| Visitor | Add operations without modifying classes | Double dispatch on node types | Many different operations on a stable set of node types |
| Chain of Responsibility | Pass request along a chain | Linear chain of handlers | One handler processes the request; not recursive aggregation |
Problems To Solve
Composite problems test whether you can identify the Component, Leaf, and Composite roles, implement recursive delegation correctly, and handle edge cases like empty composites and deep nesting.
| Difficulty | Problem | Key Insight |
|---|---|---|
| Easy | Menu System Build a restaurant menu where a MenuItem (leaf) has a name and price, and a SubMenu (composite) holds a collection of menu entries. Implement getPrice() that returns the item price for a leaf and sums all children’s prices for a sub-menu. Also implement print(indent) that renders the menu tree with indentation. | The simplest Composite exercise: one interface (MenuEntry), one leaf, one composite. The getPrice() method mirrors getSize() in the file system example — leaf returns its own value, composite sums its children. Tests basic recursive delegation and uniform treatment. |
| Easy | Organization Chart Model an organization where an Employee (leaf) has a name and salary, and a Department (composite) contains employees and sub-departments. Implement getTotalSalary() and countEmployees() across the entire tree. Ensure Department.countEmployees() counts only leaves, not sub-departments. | Tests Composite with two operations on the same tree. getTotalSalary() is a straightforward sum; countEmployees() requires distinguishing leaf-count vs. node-count. A leaf returns 1 for count and its salary for total; a composite sums both from children. |
| Medium | Expression Evaluator Build an arithmetic expression tree. A NumberExpression (leaf) holds a numeric value. An OperatorExpression (composite) holds an operator (+, −, ×, ÷) and two child expressions. Implement evaluate() that recursively computes the result, and toString() that produces a fully-parenthesized infix string like ((3 + 5) * 2). | Tests Composite in a non-collection context — the composite has exactly two children (left, right) instead of a variable-length list. Also introduces operator-specific logic in the composite node, which is more complex than simple aggregation. The toString() method tests recursive structural rendering. |
| Medium | Permission System A Permission interface has hasAccess(String resource) : boolean. A SinglePermission (leaf) grants access to one specific resource. A PermissionGroup (composite) holds child permissions and grants access if any child grants it (OR logic). Add a second composite, AllRequiredGroup, that grants access only if all children grant it (AND logic). Build a tree and test complex permission queries. | Tests Composite with boolean aggregation (OR vs. AND) instead of numeric summation. Having two different composite types that aggregate differently is a real-world scenario (role-based access). The client calls hasAccess() uniformly — it doesn’t know whether the tree uses OR, AND, or a mix of both. |
| Hard | UI Layout Engine Build a simplified UI layout system. A Widget interface has render(x, y) and getPreferredSize() : Dimension. Leaf widgets: Button, Label, TextInput. Composite widgets: HBox (lays children out horizontally), VBox (vertically). Each composite computes its preferred size from children (HBox: sum widths, max height; VBox: max width, sum heights). Implement render() that assigns positions to each child based on the layout strategy. | Tests Composite with layout computation — the composite doesn’t just aggregate a value but must compute positions for each child before delegating render(). Two different composite strategies (HBox vs. VBox) show how composites can vary their delegation logic while maintaining the same interface. This mirrors real UI frameworks (Swing, JavaFX, Flutter). |
- When asked about Composite, the interviewer wants to see: (1) a clear Component interface with operations shared by all nodes; (2) a Leaf that implements the operation directly (base case); (3) a Composite that delegates to children and aggregates results (recursive case); (4) that
add()/remove()belong on the Composite, not on the Component; (5) awareness that AWT/SwingContainer/Componentis the canonical Java example. - Stand-out answers distinguish Composite from Decorator (many children vs. one wrapper), and mention cycle prevention and immutable children lists as production concerns.