Design Patterns, Explained Simply · Structural Patterns: Fitting Objects Together

Composite

Treat a single object and a group of objects the same way, so code can work with whole trees, like folders, menus or org charts, through one interface.

The problem

A file browser shows the size of each folder. A folder contains files and other folders, nested to any depth. The first version has code like if isinstance(item, File): ... elif isinstance(item, Folder): for child in item.children: ..., and the same branching appears in size calculation, search, rendering and permission checks.

Every new kind of item (a symlink, a zip archive treated as a folder) means updating all those if chains.

Real-world analogy

An army's orders: a general gives an order to a division, the division passes it to its brigades, they pass it to companies, down to individual soldiers. The general doesn't care whether a unit is one soldier or ten thousand; they just say "advance", and each unit handles it, passing it on to its members if it has any.

Without the pattern

def total_size(item):
    if isinstance(item, File):
        return item.size
    elif isinstance(item, Folder):
        return sum(total_size(child) for child in item.children)
    # repeated in search(), render(), can_read()...

The pattern

  • Component interface: operations common to leaves and containers (size(), render()).
  • Leaf (File): a simple element that does the work itself.
  • Composite (Folder): holds children (leaves or other composites) and implements each operation by delegating to its children and combining the results.
  • Client: calls size() on any component, a single file or a whole tree, without checking which it is.

The code

Files and folders behind one interfaceTry it yourself
class File:  # leaf
    def __init__(self, name, size):
        self.name, self._size = name, size

    def size(self):
        return self._size

    def render(self, indent=0):
        return [" " * indent + f"{self.name} ({self._size} KB)"]


class Folder:  # composite: contains files and folders
    def __init__(self, name, children):
        self.name, self.children = name, children

    def size(self):
        return sum(child.size() for child in self.children)  # delegate and combine

    def render(self, indent=0):
        lines = [" " * indent + f"{self.name}/ ({self.size()} KB)"]
        for child in self.children:
            lines += child.render(indent + 2)
        return lines


project = Folder("project", [
    File("README.md", 4),
    Folder("src", [File("app.py", 12), File("utils.py", 6), Folder("assets", [File("logo.png", 40)])]),
    Folder("docs", []),
])
print("\n".join(project.render()))
print("one file is a component too:", File("notes.txt", 2).size(), "KB")

Output

project/ (62 KB)
  README.md (4 KB)
  src/ (58 KB)
    app.py (12 KB)
    utils.py (6 KB)
    assets/ (40 KB)
      logo.png (40 KB)
  docs/ (0 KB)
one file is a component too: 2 KB

project.size() walks the whole tree without a single type check. A new item type, such as a symlink, only needs to implement size() and render().

When to use it

  • Your data is a tree: folders and files, menus and menu items, UI containers and widgets, org charts, product bundles, expression trees.
  • Clients should treat one item and a group the same way ("total price" of one product or a bundle of bundles).

When not to use it

  • The structure is flat. A list is enough.
  • Leaves and containers really need different operations. Forcing them into one interface leads to leaves with methods like add_child() that throw errors (a Liskov smell).
  • Very deep or huge trees: recursion depth and repeated recomputation (like size() called at every level) may need caching.

You've already used it

  • The DOM: every Element and Text node is a Node; elements contain other nodes, and operations like textContent work on any subtree.
  • React: components render other components, and the whole UI is one tree of the same kind of thing.
  • Java Swing / AWT: Container is a Component that holds other components.

Key points

  • Composite lets clients treat single objects and groups uniformly through one interface.
  • Containers implement operations by delegating to children and combining results.
  • It removes if leaf / if container branching from client code.
  • Further reading: Refactoring Guru: Composite.

Practice questions

What problem does the Composite pattern solve?

Answer

It lets client code treat individual objects and groups of objects (trees) the same way through one interface. Containers implement each operation by delegating to their children, so clients never need if leaf / else container checks.

Name three real-world structures that suit the Composite pattern.

Answer

File systems (folders and files), UI component trees (the DOM, React, Swing containers), menus with submenus, org charts and product bundles that contain products or other bundles.

What is a common design difficulty with Composite?

Answer

Deciding where child-management methods (add, remove) go. Putting them on the shared interface makes leaves implement methods that make no sense (a Liskov smell); putting them only on composites means clients must sometimes know the concrete type.

Open in the app to mark lessons complete, take notes and track your progress.