Files and folders, menu items and submenus, employees and departments: many domains are trees where a group can contain items and other groups. Composite gives the item and the group the same interface, so your code can call delete() or print() without asking "is this one thing or many?".
Intent#
Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly. (GoF)
Analogy: a file system. Deleting a file deletes it. Deleting a folder deletes it and everything inside. Either way, you just say "delete".
The problem: type checks everywhere#
Without a shared interface, every operation has to check what kind of node it holds:
class MenuItem:
def __init__(self, name: str):
self.name = name
class MenuSection:
def __init__(self):
self.items: list[MenuItem] = []
self.subsections: list["MenuSection"] = []
def print_menu(obj: MenuItem | MenuSection) -> None:
if isinstance(obj, MenuItem):
print(obj.name)
elif isinstance(obj, MenuSection):
for item in obj.items:
print(item.name)
for sub in obj.subsections:
print_menu(sub)
# Every operation repeats this type checking
With Composite:
from abc import ABC, abstractmethod
class MenuComponent(ABC):
@abstractmethod
def print(self) -> None: ...
class MenuItem(MenuComponent):
def __init__(self, name: str):
self.name = name
def print(self) -> None:
print(self.name)
class MenuSection(MenuComponent):
def __init__(self):
self._children: list[MenuComponent] = []
def add(self, child: MenuComponent) -> None:
self._children.append(child)
def print(self) -> None:
for child in self._children:
child.print() # item or section, doesn't matter
Structure#
Leaves and groups share one interface, and groups hold children of that interface.
| Participant | Role |
|---|---|
| Component | Common interface for leaves and composites |
| Leaf | An individual object with no children |
| Composite | A container that implements Component and holds a list of Components (leaves or other composites) |
The trick is recursive composition: a composite's children are Components, so trees can nest to any depth. A restaurant menu shows it:
- Menu (composite)
- Breakfast (composite)
- Pancakes (leaf)
- Eggs (leaf)
- Dinner (composite)
- Steak (leaf)
- Desserts (composite)
- Cake (leaf)
- Ice cream (leaf)
- Breakfast (composite)
Example: smart home#
A DeviceGroup is itself a device, so rooms can hold devices and floors can hold rooms:
from abc import ABC, abstractmethod
# Component
class SmartDevice(ABC):
@abstractmethod
def turn_on(self) -> None: ...
@abstractmethod
def turn_off(self) -> None: ...
# Leaves
class AirConditioner(SmartDevice):
def turn_on(self) -> None:
print("AC on")
def turn_off(self) -> None:
print("AC off")
class Fan(SmartDevice):
def turn_on(self) -> None:
print("Fan on")
def turn_off(self) -> None:
print("Fan off")
# Composite: is a SmartDevice AND holds SmartDevices
class DeviceGroup(SmartDevice):
def __init__(self, name: str):
self._name = name
self._devices: list[SmartDevice] = []
def add(self, device: SmartDevice) -> None:
self._devices.append(device)
def remove(self, device: SmartDevice) -> None:
self._devices.remove(device)
def turn_on(self) -> None:
print(f"{self._name}: turning on")
for device in self._devices:
device.turn_on() # recursion happens naturally for a group
def turn_off(self) -> None:
for device in self._devices:
device.turn_off()
if __name__ == "__main__":
room1 = DeviceGroup("Room 1")
room1.add(AirConditioner())
room1.add(Fan())
floor1 = DeviceGroup("Floor 1")
floor1.add(room1) # a group inside a group
floor1.add(Fan()) # and a leaf at the same level
floor1.turn_on() # client doesn't care what's inside
When you call floor1.turn_on():
floor1loops over its children.- It finds
room1(a composite) and callsroom1.turn_on(). room1loops over its children and callsturn_on()on the AC and the fan.- Back in
floor1, the next child (a fan) is turned on directly.
Real-world examples#
Any tree of items and groups is a candidate:
- GUI toolkits: in Tkinter, a
Framecan contain buttons and otherFrames. - The HTML DOM: elements contain text nodes and other elements.
- Python's
astmodule: syntax-tree nodes contain other nodes. - General:
- file systems, org charts, menus
- graphics editors (shapes and groups)
- document structure (text inside sections inside chapters)
- math expressions (
(2 + 3) * (4 - 1): numbers are leaves, operators are composites)
- Business:
- product catalogs (products inside categories)
- tasks inside projects, items inside shipping boxes
- bills of materials (parts inside assemblies)
When to use it#
Composite fits when:
- Your data is a part-whole hierarchy (a tree).
- Clients should treat single items and groups the same way.
- Structures are recursive or change at runtime.
- You want to get rid of
isinstancechecks in client code.
When not to use it#
Skip it when:
- The structure is flat.
- Leaves and groups need very different operations.
- You need strict, type-checked rules about what can contain what.
- It's only one or two levels deep and never nests further.
Pitfalls#
Watch for these traps:
- Overly general containers. Nothing stops
folder.add(PrinterDriver()); add runtime checks if some children aren't allowed. - Child-management methods on leaves. If
add()/remove()live on Component for uniformity, leaves must throw or ignore them. Putting them only on Composite is safer but less uniform; choose deliberately. - Deep recursion on very deep or very large trees can be slow, or hit Python's recursion limit.
- Cycles. Adding a group to itself (directly or indirectly) causes infinite recursion.
Composite vs similar patterns#
Composite is easy to mix up with these:
| Pattern | Difference |
|---|---|
| Decorator | Wraps one object in a linear chain; Composite manages many children in a tree |
| Chain of Responsibility | Usually one handler processes a request; in Composite, all children are visited |
| Flyweight | About memory (sharing); Composite is about structure. Leaves of a large tree are often flyweights |
| Iterator | About traversal; often used to walk a Composite |
Key takeaways#
- Composite = tree of objects where leaves and groups share one interface.
- A composite implements the component interface and holds a list of components.
- Operations recurse naturally down the tree; clients never type-check.
- Decide where
add/removelive: uniformity vs type safety.