6 min read
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
Core Concepts
SOLID Principles
Creational Patterns
Structural Patterns
Tier 2
Tier 3
Tier 4
Tier 5
Composite Pattern
1. What Is It?
The pattern lets you compose objects into tree structures to represent part-whole hierarchies. The core insight is that clients can treat individual objects (leaves) and compositions of objects (composites) uniformly through a single — no type-checking required.
Without it, client code must constantly ask "is this a leaf or a container?" before acting on an object. This conditional logic scatters throughout the codebase, breaks Open/Closed, and makes adding new component types painful.
A developer is building a file system UI where folders can contain files or other folders. Without the Composite pattern, their render function starts with if (node.type === 'file') { ... } else if (node.type === 'folder') { ... }. This check is repeated in every function that traverses the tree. What is the primary problem this approach introduces?
2. How It Works
Participants:
- Component — (or ) declaring operations common to both leaves and composites (e.g.,
render(),getPrice()) - Leaf — a concrete component with no children; implements the Component directly
- — a concrete component that has children (a list of Components); implements Component by delegating to its children and optionally adding its own logic
- Client — works with all components through the Component interface, never distinguishing leaf from
Step-by-step:
- Define the
Componentinterface with the operation(s) both leaves and composites must support - Implement
Leaf— no children, just the operation - Implement
— holds a list ofComponentchildren; its operation iterates children and aggregates results - Client code calls
component.operation()on the root — the tree recurses naturally
Python:
from abc import ABC, abstractmethod from typing import List class PriceComponent(ABC): @abstractmethod def get_price(self) -> float: pass class Item(PriceComponent): def __init__(self, name: str, price: float) -> None: self._name = name self._price = price def get_price(self) -> float: return self._price def __repr__(self) -> str: return f"Item({self._name}, ${self._price:.2f})" class Bundle(PriceComponent): def __init__(self, name: str) -> None: self._name = name self._children: List[PriceComponent] = [] def add(self, component: PriceComponent) -> None: self._children.append(component) def remove(self, component: PriceComponent) -> None: self._children.remove(component) def get_price(self) -> float: return sum(child.get_price() for child in self._children) def __repr__(self) -> str: return f"Bundle({self._name}, ${self.get_price():.2f})" # Usage — client treats Item and Bundle identically laptop = Item("Laptop", 999.99) mouse = Item("Mouse", 29.99) keyboard = Item("Keyboard", 79.99) peripherals = Bundle("Peripherals") peripherals.add(mouse) peripherals.add(keyboard) workstation = Bundle("Workstation Bundle") workstation.add(laptop) workstation.add(peripherals) print(workstation.get_price()) # 1109.97 — recurses transparently
Java:
import java.util.ArrayList; import java.util.List; // Component public interface PriceComponent { double getPrice(); } // Leaf public class Item implements PriceComponent { private final String name; private final double price; public Item(String name, double price) { this.name = name; this.price = price; } @Override public double getPrice() { return price; } } // Composite public class Bundle implements PriceComponent { private final String name; private final List<PriceComponent> children = new ArrayList<>(); public Bundle(String name) { this.name = name; } public void add(PriceComponent component) { children.add(component); } public void remove(PriceComponent component) { children.remove(component); } @Override public double getPrice() { return children.stream() .mapToDouble(PriceComponent::getPrice) .sum(); } } // Client public class ShoppingCart { public double calculateTotal(PriceComponent component) { return component.getPrice(); // works for both Item and Bundle } public static void main(String[] args) { Item laptop = new Item("Laptop", 999.99); Item mouse = new Item("Mouse", 29.99); Item keyboard = new Item("Keyboard", 79.99); Bundle peripherals = new Bundle("Peripherals"); peripherals.add(mouse); peripherals.add(keyboard); Bundle workstation = new Bundle("Workstation Bundle"); workstation.add(laptop); workstation.add(peripherals); ShoppingCart cart = new ShoppingCart(); System.out.println(cart.calculateTotal(workstation)); // 1109.97 } }
In a Composite pattern implementation for a shopping cart system, a Bundle class holds a list of PriceComponent children and implements getPrice() by summing each child's getPrice(). A ShoppingCart client calls getPrice() on the root bundle, which itself contains individual Item objects and nested Bundle objects. What allows the client to call a single getPrice() on the root without any special-casing for leaves versus composites?
3. Variants & Comparisons
Transparency vs. Safety variants:
| Variant | How It Works | Pros | Cons | Best For |
|---|---|---|---|---|
| Transparent | add/remove/getChildren on Component interface | Uniform client code, no casts | Leaf must implement no-op add/remove | When uniformity is paramount |
| Safe | add/remove only on Composite class | No meaningless leaf methods | Client must downcast to use Composite ops | When type safety matters more |
The GoF book uses the transparent variant. Most modern codebases lean safe (e.g., avoid UnsupportedOperationException on leaves).
vs. : Both involve a component wrapping other components. is about tree hierarchies and ; is about adding behavior to a single object. A Composite has many children; a Decorator wraps exactly one.
4. When to Use It (and When NOT To)
Use when:
- You have a tree-shaped hierarchy (file system, UI component tree, org chart, bill of materials)
- Clients should not need to distinguish between a leaf and a branch
- You want recursive operations (sum, render, serialize) to propagate naturally through the tree
Don't use when:
- The hierarchy is flat or has only one level — the pattern adds unnecessary
- Components are too different to share a common without forcing awkward no-ops
Decision triggers:
- "I keep writing
if isinstance(x, Container)before calling children" → reach for - "I need to calculate a total/size/count across a nested structure" → handles this recursively
Anti-patterns:
- Putting child-management methods on the Component when using the Safe variant defeats the purpose and causes
UnsupportedOperationExceptionsurprises - Making
CompositeextendLeaf— hierarchy should be from the Component , not from a concrete class
A backend developer is building a reporting tool where each report can contain either individual data widgets or nested report sections, which themselves contain more widgets or sections. The developer notices they keep writing if isinstance(node, ReportSection): process_children(node) before recursing. Which of the following best describes why the Composite pattern is a good fit here, and what benefit it provides?
5. Real-World Usage
1. Java Swing / AWT UI: java.awt.Component is the Component . java.awt.Container is the . JButton, JLabel are Leaves. JPanel is a that holds other Components. Swing renders the entire UI tree by recursively calling paint() on every component — the client never cares whether it's a leaf widget or a panel.
2. HTML/DOM: Every HTML element is a node. A <div> is a Composite that holds children; a text node is a Leaf. document.getElementById("root").remove() deletes a subtree without knowing its depth.
3. java.io.File: java.io.File models both files (leaves) and directories (composites). file.listFiles() returns null for a leaf and a list for a directory — a partial Composite implementation.
A developer calls document.getElementById('root').remove() on a deeply nested DOM tree containing dozens of <div> containers and hundreds of child elements. Which property of the Composite pattern explains why this single call successfully removes the entire subtree?
6. Interview Cheat Sheet
Key sentences:
- " lets clients treat leaves and containers uniformly through a shared — no
instanceofchecks required." - "It's ideal when your domain naturally forms a tree: file systems, UI hierarchies, bill-of-materials, org charts."
- "The recursive structure means operations like
getPrice()orrender()propagate through the tree without any orchestration code." - "The main trade-off is transparency vs. safety: putting
add/removeon the Component is more uniform but forces leaves to handle operations that don't make sense for them."
Common follow-up questions:
Q: What's the difference between and ? A: Both wrap a Component interface, but Composite aggregates multiple children for tree structures; wraps exactly one component to add behavior.
Q: How do you handle the fact that add() doesn't make sense on a Leaf?
A: Two options — Safe variant (keep add/remove on Composite only, client must cast) or Transparent variant (put them on Component with a default no-op or exception on Leaf). Safe is cleaner; Transparent is more uniform. I'd choose based on how often clients need the child-management API.
Q: Where does Composite violate LSP?
A: In the Transparent variant, Leaf.add() throwing UnsupportedOperationException violates LSP — callers of the Component interface can't safely assume add() works.
Connections:
- Composite + Iterator → traverse the tree without exposing internal structure
- Composite + Visitor → add operations (e.g.,
serialize,validate) to a composite tree without modifying each node class - Composite + Decorator → often appear together in UI frameworks; Composite for hierarchy, Decorator for behavior
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.