Skip to main content

Patternintermediate

Composite

Overview

Composite organizes objects into tree structures and lets the client treat individual objects and compositions uniformly.

The gain is specific: the client no longer needs to know whether it is dealing with a leaf or a node. Where there were conditionals, there is now one call.

Problem

A hierarchical structure in which an element may contain others of the same type: directories with files and subdirectories, groups of graphical elements, menu items with submenus, expressions with subexpressions.

Without the pattern, all code that walks the structure has to distinguish:

if it is a file: add its size
if it is a directory: for each child, repeat

That conditional replicates in every operation — compute size, render, count, search. Adding a node type requires touching all of them.

Core Concepts

The structure

Look at where the contains arrow points: at the interface, not at the composite. Either implementer can be a child.

The composite implements the same interface as the leaf and delegates to the children. The recursion sits inside the structure, not in the client.

The central decision: uniformity or safety

The GoF presents two variants, and choosing between them is the pattern's trade-off.

Transparent — the Component interface declares add and remove. The client treats everything the same; the leaf has to deal with operations that make no sense for it, typically by throwing.

Safe — only the composite has add and remove. There is no meaningless operation; the client has to check the type in order to compose.

Transparent wins on uniformity and loses on type safety; safe, the reverse. The choice is binary when the interface has to be closed and the platform offers no safe query — outside that there is a middle ground: a query accessor, asComposite(), returning empty on the leaf. It preserves the uniform traversal and gives whoever composes a total check, with no instanceof; in a language with sealed types and pattern matching, the cost of that check is lower still.

Either way, choosing requires knowing whether the client composes or merely traverses.

When the client only traverses, the safe variant costs nothing and is preferable.

Composite and recursion

The structure is naturally recursive, and that brings two real concerns: depth — the stack in very deep trees — and cycles, which produce infinite recursion if the structure allows a node to contain an ancestor.

When to Use

  • There is a genuine part-whole hierarchy in the domain.
  • The client should treat leaves and compositions identically.
  • Operations apply recursively to the whole structure.
  • New leaf types appear frequently.

When Not to Use

When the hierarchy is not part-whole. Inheritance is not composition. If a type does not contain others of the same type, the pattern does not apply.

When leaf and composite have very different behaviour. Forcing a common interface produces meaningless methods on one side, and the client ends up checking the type anyway — losing the benefit and keeping the cost.

When the structure is shallow and fixed. Two known levels do not justify the generality.

When type safety matters more than uniformity. See the decision above.

When operations need context from the path. If a node's behaviour depends on who its ancestors are, the uniformity breaks and the pattern starts getting in the way.

Alternatives

  • A simple list — when the structure is shallow.
  • Visitor — when the operations vary more than the node types; frequently used together with Composite.
  • Iterator — when traversal is the only need.
  • A data structure with no class hierarchy — a generic tree with data in the nodes.

Trade-offs

CompositeConditional in the client
Uniform clientReplicated conditional
A new leaf type does not touch the clientTouches every conditional
Interface with meaningless operations for leavesPrecise types
Encapsulated recursionExplicit and visible
Implicit traversal, hard to controlFull control

Failure Modes

Leaf that throws. A consequence of the transparent variant, and a declared Liskov violation.

Cycle in the structure. Infinite recursion.

Excessive depth. Stack overflow in a recursive traversal.

Empty composite treated as a leaf. An ambiguity that produces subtle defects.

Hidden expensive operation. A simple call walks thousands of nodes, and nothing in the code suggests it.

Common Mistakes

Applying it to an inheritance hierarchy. It is not the same as a part-whole hierarchy.

Choosing the variant without thinking. It is the pattern's decision.

Not handling cycles. If the structure allows one, someone will create it.

Ignoring traversal cost. A uniform operation can be O(n) with no warning.

Where it appears in practice

GUI trees. A container is a component that contains components. Rendering, measuring and propagating events are uniform operations over the tree.

File systems. Directories and files with common operations — size, permissions, path.

Syntax trees. An expression contains subexpressions; evaluating is recursive. It is where Composite and Visitor most frequently appear together.

Document structures. The DOM in browsers: nodes containing nodes, with uniform operations.

In all four, the part-whole hierarchy is intrinsic to the domain — nobody invented it in order to apply the pattern. That is the sign Composite is appropriate: the tree already exists in the problem.

Real-World Example

A permissions system modelled groups containing users and other groups. Checking whether someone has a permission required recursive traversal.

The first implementation used transparent Composite: Principal with add and remove, and User throwing on both.

The problem appeared when the admin interface started building the structure: it needed to check the type before composing, which nullified the uniformity — and still kept the exceptions.

The change to the safe variant removed add and remove from Principal. The permission-checking code, which only traverses, stayed uniform. The admin code, which composes, came to work with Group explicitly.

It turned out the two clients had different needs, and that the transparent variant was trying to serve both with a single interface.

A cycle also appeared: an administrator put a group inside one of its descendants. The check came to track visited nodes, which should have been done from the start.

  • Decorator — similar structure, different purpose.
  • Visitor — operations over the structure.
  • Iterator — traversal.

Practical Exercise

Look in your system for structures in which an element contains elements of the same type.

For each, check: is there a type conditional replicated across the traversals? Does the structure allow a cycle? How much does the most common operation cost in number of nodes?

Interview Questions

  • What are Composite's two variants and what is traded between them?
  • How does Composite differ from an ordinary inheritance hierarchy?
  • What risks does recursion bring in this pattern?

Further Exploration

  • Gamma, Erich et al. Design Patterns. Addison-Wesley, 1994.
Finished reading this document?Your progress is saved in this browser only.