Skip to content

Composite Pattern — Tree Structures of Objects

DodaTech Updated 2026-06-24 4 min read

What You'll Learn

You will learn how the Composite pattern lets you build recursive tree structures where leaves and containers share a common interface, simplifying client code. You will understand the trade-off between transparency and safety in Composite design.

Why It Matters

Hierarchies appear everywhere — UI render trees, organisational charts, file systems, and menu systems. Without Composite, clients must distinguish between leaf and container nodes with instanceof checks. Composite eliminates this by uniting both under a single component interface. Consider a file system where you want to calculate the total size: without Composite, you'd recursively walk directories with conditional logic for files versus directories. With Composite, you call get_size() on any node and let polymorphism handle the rest.

Real-World Use

A graphic editor treats both simple shapes and groups of shapes identically for dragging, resizing, and rendering. DodaTech's workflow designer uses Composite to build pipelines where a single step or an entire sub-pipeline can be validated, timed, and monitored the same way — a sub-pipeline looks exactly like a single step to the monitoring system.

The Pattern

Component declares the common interface. Leaf represents end objects. Composite stores child components and implements child-related operations.

from abc import ABC, abstractmethod

class FileSystemComponent(ABC):
    @abstractmethod
    def get_size(self) -> int:
        pass

    @abstractmethod
    def display(self, indent: str = "") -> str:
        pass

class File(FileSystemComponent):
    def __init__(self, name: str, size: int):
        self._name = name
        self._size = size

    def get_size(self) -> int:
        return self._size

    def display(self, indent: str = "") -> str:
        return f"{indent}{self._name} ({self._size} bytes)"

class Directory(FileSystemComponent):
    def __init__(self, name: str):
        self._name = name
        self._children: list[FileSystemComponent] = []

    def add(self, component: FileSystemComponent):
        self._children.append(component)

    def remove(self, component: FileSystemComponent):
        self._children.remove(component)

    def get_size(self) -> int:
        return sum(c.get_size() for c in self._children)

    def display(self, indent: str = "") -> str:
        lines = [f"{indent}{self._name}/"]
        for child in self._children:
            lines.append(child.display(indent + "  "))
        return "\n".join(lines)
root = Directory("root")
docs = Directory("docs")
docs.add(File("readme.md", 200))
docs.add(File("license.md", 50))
src = Directory("src")
src.add(File("main.py", 1024))
src.add(File("utils.py", 512))
root.add(docs)
root.add(src)
print(root.display())
print(f"Total size: {root.get_size()} bytes")
root/
  docs/
    readme.md (200 bytes)
    license.md (50 bytes)
  src/
    main.py (1024 bytes)
    utils.py (512 bytes)
Total size: 1786 bytes

Structure

classDiagram
    class Component {
        <>
        +operation()
        +add(Component)
        +remove(Component)
        +getChild(int)
    }
    class Leaf {
        +operation()
    }
    class Composite {
        +operation()
        +add(Component)
        +remove(Component)
        +getChild(int)
    }
    Leaf ..|> Component
    Composite ..|> Component
    Composite --> "*" Component : children

Real-World Usage

  • React component tree — every component, whether a <div> leaf or a <App> Composite, exposes the same render API.
  • Java AWT/Swing ContainerContainer.add(Component) accepts both simple widgets and nested containers.
  • XML/HTML DOM — every node implements Node, and Element nodes can contain child nodes.
  • File system APIsos.walk() in Python treats directories and files through the same iteration protocol.
  • Decorator has a similar structure but adds behaviour rather than aggregating children.
  • Iterator is used to traverse Composite structures.
  • Visitor lets you define operations on Composite elements without changing their classes.
  • Flyweight helps share leaf data across large Composite trees.

Pros and Cons

Pros Cons
Makes client code simple and uniform Can violate the Interface Segregation Principle with overly general components
Easy to add new component types Tree traversal can become expensive for deep hierarchies
Natural fit for recursive structures Adding type-specific behaviour may require instanceof checks
Transparent to clients whether they work with leaves or composites Child ordering and indexing adds complexity

The code demonstrates the beautiful simplicity of Composite. Both File and Directory implement FileSystemComponent. A Directory can contain any number of FileSystemComponent objects — including other Directory instances. When display() or get_size() is called on the root, the call propagates recursively through the entire tree, with each node handling its own contribution.

Practice Questions

  1. How would you modify Composite to support a parent reference from child to container? What would this enable?
  2. Implement a safety policy — disallow adding children to a Leaf at compile time.
  3. What Caching strategies can optimise repeated get_size() calls in a stable tree where files rarely change?
  4. Combine Composite with Visitor to implement an export operation across all nodes.

Challenge

Add a SymLink class that delegates to another FileSystemComponent. This introduces a different kind of leaf that can point to files or directories, potentially creating cycles. How would you handle this?

Real-World Task

Examine your UI component hierarchy. Identify deeply nested conditional logic that distinguishes between container and leaf nodes. Refactor it using Composite and use DodaTech's tree visualiser to verify the hierarchy.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro