Composite Pattern

6 min read

Reading Progress0%
Object-Oriented Design Index
Tier 1 -- Foundations
Tier 2
Tier 3
Tier 4
Tier 5
Object-Oriented Design Index
Tier 1 -- Foundations
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.


QUICK CHECK

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?

Choose one answer

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:

  1. Define the Component interface with the operation(s) both leaves and composites must support
  2. Implement Leaf — no children, just the operation
  3. Implement — holds a list of Component children; its operation iterates children and aggregates results
  4. 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
    }
}

QUICK CHECK

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?

Choose one answer

3. Variants & Comparisons

Transparency vs. Safety variants:

VariantHow It WorksProsConsBest For
Transparentadd/remove/getChildren on Component interfaceUniform client code, no castsLeaf must implement no-op add/removeWhen uniformity is paramount
Safeadd/remove only on Composite classNo meaningless leaf methodsClient must downcast to use Composite opsWhen 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 UnsupportedOperationException surprises
  • Making Composite extend Leaf — hierarchy should be from the Component , not from a concrete class

QUICK CHECK

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?

Choose one answer

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.


QUICK CHECK

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?

Choose one answer

6. Interview Cheat Sheet

Key sentences:

  1. " lets clients treat leaves and containers uniformly through a shared — no instanceof checks required."
  2. "It's ideal when your domain naturally forms a tree: file systems, UI hierarchies, bill-of-materials, org charts."
  3. "The recursive structure means operations like getPrice() or render() propagate through the tree without any orchestration code."
  4. "The main trade-off is transparency vs. safety: putting add/remove on 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.