Skip to content

Composite Pattern

Compose objects into tree structures - treat individual objects and compositions uniformly!

Composite Pattern: Treating Individual Objects and Compositions Uniformly

Section titled “Composite Pattern: Treating Individual Objects and Compositions Uniformly”

Now let’s explore the Composite Pattern - a powerful structural design pattern that lets you compose objects into tree structures and work with them uniformly.

Imagine you’re organizing files and folders on your computer. A folder can contain files AND other folders. When you want to get the total size, you need to treat both files and folders the same way - folders recursively sum up their contents. The Composite Pattern makes this possible by treating individual objects (files) and compositions (folders) uniformly.

The Composite Pattern composes objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.

The Composite Pattern is useful when:

  1. You want to represent part-whole hierarchies - Objects that contain other objects
  2. You want clients to ignore - The difference between individual objects and compositions
  3. You want to treat objects uniformly - Same interface for leaves and composites
  4. You have tree structures - Hierarchical data that needs uniform treatment
  5. You want recursive operations - Operations that work on both leaves and composites

What Happens If We Don’t Use Composite Pattern?

Section titled “What Happens If We Don’t Use Composite Pattern?”

Without the Composite Pattern, you might:

  • Type checking everywhere - Need to check if object is leaf or composite
  • Different interfaces - Leaves and composites have different methods
  • Complex client code - Clients need to handle leaves and composites differently
  • Code duplication - Similar logic repeated for leaves and composites
  • Hard to extend - Adding new types requires changes everywhere

Let’s start with a super simple example that anyone can understand!

Diagram

Here’s how the Composite Pattern works in practice - showing how leaves and composites work uniformly:

sequenceDiagram
    participant Client
    participant Folder as Folder (Composite)
    participant File1 as File1 (Leaf)
    participant File2 as File2 (Leaf)
    participant SubFolder as SubFolder (Composite)
    
    Client->>Folder: get_size()
    activate Folder
    Folder->>File1: get_size()
    activate File1
    File1-->>Folder: 100 bytes
    deactivate File1
    Folder->>File2: get_size()
    activate File2
    File2-->>Folder: 200 bytes
    deactivate File2
    Folder->>SubFolder: get_size()
    activate SubFolder
    SubFolder-->>Folder: 150 bytes
    deactivate SubFolder
    Folder->>Folder: Sum: 100 + 200 + 150
    Folder-->>Client: 450 bytes
    deactivate Folder
    
    Note over Client,SubFolder: Client treats File and Folder\nthe same way - uniform interface!

You’re building a file system. You have files (individual objects) and folders (containers). Without Composite Pattern:

bad_file_system.py
# ❌ Without Composite Pattern - Different interfaces!
class File:
"""File - individual object"""
def __init__(self, name: str, size: int):
self.name = name
self.size = size
def get_size(self) -> int:
return self.size
class Folder:
"""Folder - container"""
def __init__(self, name: str):
self.name = name
self.files = [] # Only files, not folders!
self.folders = [] # Separate list for folders
def add_file(self, file: File):
self.files.append(file)
def add_folder(self, folder: 'Folder'):
self.folders.append(folder)
def get_size(self) -> int:
total = 0
# Handle files
for file in self.files:
total += file.get_size()
# Handle folders separately
for folder in self.folders:
total += folder.get_size() # Recursive
return total
# Problem: Client needs to know about files vs folders!
def get_total_size(folder: Folder) -> int:
total = 0
# Different handling for files and folders
for file in folder.files:
total += file.get_size()
for subfolder in folder.folders:
total += get_total_size(subfolder) # Recursive call
return total
# Problems:
# - Different interfaces for File and Folder
# - Client needs to check type (is it file or folder?)
# - Can't treat files and folders uniformly
# - Hard to extend (add new types)

Problems:

  • Different interfaces - Files and folders handled differently
  • Type checking - Clients need to check if object is file or folder
  • Can’t treat uniformly - No common interface
  • Hard to extend - Adding new types requires changes everywhere
classDiagram
    class Component {
        <<interface>>
        +get_size() int
        +get_name() string
    }
    class File {
        -name: string
        -size: int
        +get_size() int
        +get_name() string
    }
    class Folder {
        -name: string
        -children: List~Component~
        +get_size() int
        +get_name() string
        +add(component) void
        +remove(component) void
    }
    
    Component <|.. File : implements (leaf)
    Component <|.. Folder : implements (composite)
    Folder --> Component : contains (children)
    
    note for Component "Common interface for leaves and composites"
    note for File "Leaf - individual object"
    note for Folder "Composite - contains components"
composite_file_system.py
from abc import ABC, abstractmethod
from typing import List
# Step 1: Define the component interface
class FileSystemComponent(ABC):
"""Component interface - common interface for leaves and composites"""
@abstractmethod
def get_size(self) -> int:
"""Get the size of the component"""
pass
@abstractmethod
def get_name(self) -> str:
"""Get the name of the component"""
pass
# Step 2: Implement the leaf (File)
class File(FileSystemComponent):
"""File - leaf node in the tree"""
def __init__(self, name: str, size: int):
self.name = name
self.size = size
def get_size(self) -> int:
"""Get file size"""
return self.size
def get_name(self) -> str:
"""Get file name"""
return self.name
# Step 3: Implement the composite (Folder)
class Folder(FileSystemComponent):
"""Folder - composite node in the tree"""
def __init__(self, name: str):
self.name = name
self.children: List[FileSystemComponent] = [] # Can contain files or folders!
def add(self, component: FileSystemComponent) -> None:
"""Add a component (file or folder)"""
self.children.append(component)
def remove(self, component: FileSystemComponent) -> None:
"""Remove a component"""
if component in self.children:
self.children.remove(component)
def get_size(self) -> int:
"""Get total size - recursively sums children"""
total = 0
for child in self.children:
total += child.get_size() # Works for both files and folders!
return total
def get_name(self) -> str:
"""Get folder name"""
return self.name
# Usage - Clean and uniform!
def main():
# Create files (leaves)
file1 = File("document.txt", 100)
file2 = File("image.jpg", 200)
file3 = File("video.mp4", 500)
# Create folders (composites)
root = Folder("Root")
documents = Folder("Documents")
media = Folder("Media")
# Build the tree structure
documents.add(file1)
media.add(file2)
media.add(file3)
root.add(documents)
root.add(media)
# Client treats files and folders uniformly!
print(f"File size: {file1.get_size()} bytes")
print(f"Documents folder size: {documents.get_size()} bytes")
print(f"Media folder size: {media.get_size()} bytes")
print(f"Root folder size: {root.get_size()} bytes")
print("\n✅ Composite Pattern allows uniform treatment of files and folders!")
if __name__ == "__main__":
main()

Real-World Software Example: Organization Hierarchy

Section titled “Real-World Software Example: Organization Hierarchy”

Now let’s see a realistic software example - an organization system that needs to calculate total salary for employees and departments.

You’re building an HR system that needs to calculate total salary. Employees can be individual workers or managers (who have subordinates). Without Composite Pattern:

bad_organization.py
# ❌ Without Composite Pattern - Different handling!
class Employee:
"""Individual employee"""
def __init__(self, name: str, salary: float):
self.name = name
self.salary = salary
def get_salary(self) -> float:
return self.salary
class Manager:
"""Manager with subordinates"""
def __init__(self, name: str, salary: float):
self.name = name
self.salary = salary
self.subordinates = [] # Only employees, not managers!
self.managers = [] # Separate list for managers
def add_employee(self, employee: Employee):
self.subordinates.append(employee)
def add_manager(self, manager: 'Manager'):
self.managers.append(manager)
def get_salary(self) -> float:
total = self.salary
# Handle employees
for emp in self.subordinates:
total += emp.get_salary()
# Handle managers separately
for mgr in self.managers:
total += mgr.get_salary() # Recursive
return total
# Problem: Client needs to handle employees and managers differently!
def calculate_total_salary(org) -> float:
if isinstance(org, Employee):
return org.get_salary()
elif isinstance(org, Manager):
total = org.salary
for emp in org.subordinates:
total += calculate_total_salary(emp)
for mgr in org.managers:
total += calculate_total_salary(mgr)
return total
# Problems:
# - Different interfaces for Employee and Manager
# - Type checking required
# - Can't treat uniformly

Problems:

  • Different interfaces - Employees and managers handled differently
  • Type checking - Need to check if object is employee or manager
  • Can’t treat uniformly - No common interface
composite_organization.py
from abc import ABC, abstractmethod
from typing import List
# Step 1: Define the component interface
class OrganizationComponent(ABC):
"""Component interface - common interface for employees and departments"""
@abstractmethod
def get_salary(self) -> float:
"""Get the salary of the component"""
pass
@abstractmethod
def get_name(self) -> str:
"""Get the name of the component"""
pass
# Step 2: Implement the leaf (Employee)
class Employee(OrganizationComponent):
"""Employee - leaf node"""
def __init__(self, name: str, salary: float):
self.name = name
self.salary = salary
def get_salary(self) -> float:
"""Get employee salary"""
return self.salary
def get_name(self) -> str:
"""Get employee name"""
return self.name
# Step 3: Implement the composite (Department)
class Department(OrganizationComponent):
"""Department - composite node (can contain employees and sub-departments)"""
def __init__(self, name: str):
self.name = name
self.members: List[OrganizationComponent] = [] # Can contain employees or departments!
def add(self, component: OrganizationComponent) -> None:
"""Add a component (employee or department)"""
self.members.append(component)
def remove(self, component: OrganizationComponent) -> None:
"""Remove a component"""
if component in self.members:
self.members.remove(component)
def get_salary(self) -> float:
"""Get total salary - recursively sums members"""
total = 0.0
for member in self.members:
total += member.get_salary() # Works for both employees and departments!
return total
def get_name(self) -> str:
"""Get department name"""
return self.name
# Usage - Clean and uniform!
def main():
# Create employees (leaves)
emp1 = Employee("Alice", 50000.0)
emp2 = Employee("Bob", 60000.0)
emp3 = Employee("Charlie", 55000.0)
emp4 = Employee("Diana", 70000.0)
# Create departments (composites)
engineering = Department("Engineering")
sales = Department("Sales")
company = Department("Company")
# Build the hierarchy
engineering.add(emp1)
engineering.add(emp2)
sales.add(emp3)
sales.add(emp4)
company.add(engineering)
company.add(sales)
# Client treats employees and departments uniformly!
print(f"Employee salary: ${emp1.get_salary():,.2f}")
print(f"Engineering department salary: ${engineering.get_salary():,.2f}")
print(f"Sales department salary: ${sales.get_salary():,.2f}")
print(f"Company total salary: ${company.get_salary():,.2f}")
print("\n✅ Composite Pattern allows uniform treatment of employees and departments!")
if __name__ == "__main__":
main()

There are different ways to implement the Composite Pattern:

All methods in component interface, leaves implement empty methods for composite operations:

transparent_composite.py
# Transparent Composite - all methods in interface
class Component:
def operation(self): pass
def add(self, component): pass # Leaf returns None or raises
def remove(self, component): pass # Leaf returns None or raises
def get_children(self): pass # Leaf returns empty list
class Leaf(Component):
def operation(self):
return "Leaf operation"
def add(self, component):
raise NotImplementedError("Leaf cannot have children")
def remove(self, component):
raise NotImplementedError("Leaf cannot have children")
def get_children(self):
return []
class Composite(Component):
def __init__(self):
self.children = []
def operation(self):
return "Composite operation"
def add(self, component):
self.children.append(component)
def remove(self, component):
self.children.remove(component)
def get_children(self):
return self.children

Pros: Uniform interface, no type checking needed
Cons: Leaves have methods they don’t use (can raise exceptions)

Only leaf operations in component interface, composite operations in composite class:

safe_composite.py
# Safe Composite - only leaf operations in interface
class Component:
def operation(self): pass
class Leaf(Component):
def operation(self):
return "Leaf operation"
class Composite(Component):
def __init__(self):
self.children = []
def operation(self):
return "Composite operation"
def add(self, component): # Only in Composite
self.children.append(component)
def remove(self, component): # Only in Composite
self.children.remove(component)

Pros: Type-safe, leaves don’t have unused methods
Cons: Need type checking to use composite operations


Use Composite Pattern when:

You want to represent part-whole hierarchies - Objects that contain other objects
You want clients to ignore - The difference between individual objects and compositions
You want to treat objects uniformly - Same interface for leaves and composites
You have tree structures - Hierarchical data that needs uniform treatment
You want recursive operations - Operations that work on both leaves and composites

Don’t use Composite Pattern when:

Simple flat structure - No hierarchy, just a list
Different operations - Leaves and composites need very different operations
Performance critical - Recursive operations can be slower
Over-engineering - Don’t add complexity for simple cases


no_common_interface.py
# ❌ Bad: No common interface
class File:
def get_size(self): return 100
class Folder:
def get_size(self): return 200
def add(self, item): pass
# Client needs type checking!
def get_total_size(item):
if isinstance(item, File):
return item.get_size()
elif isinstance(item, Folder):
return item.get_size() # But can't treat uniformly
# ✅ Good: Common interface
class Component:
def get_size(self): pass
class File(Component):
def get_size(self): return 100
class Folder(Component):
def get_size(self): return 200
# Client treats uniformly!
def get_total_size(component: Component):
return component.get_size() # Works for both!

Mistake 2: Composite Not Delegating to Children

Section titled “Mistake 2: Composite Not Delegating to Children”
wrong_delegation.py
# ❌ Bad: Composite doesn't delegate to children
class Folder:
def __init__(self):
self.children = []
self.size = 0 # Bad: Storing size separately
def get_size(self):
return self.size # Bad: Doesn't sum children!
# ✅ Good: Composite delegates to children
class Folder:
def __init__(self):
self.children = []
def get_size(self):
total = 0
for child in self.children:
total += child.get_size() # Good: Delegates to children
return total
circular_reference.py
# ❌ Bad: Allowing circular references
folder1 = Folder("Folder1")
folder2 = Folder("Folder2")
folder1.add(folder2)
folder2.add(folder1) # Bad: Circular reference!
# ✅ Good: Prevent circular references
class Folder:
def add(self, component):
if component == self:
raise ValueError("Cannot add folder to itself")
if self._would_create_cycle(component):
raise ValueError("Would create circular reference")
self.children.append(component)
def _would_create_cycle(self, component):
# Check if adding component would create cycle
if component == self:
return True
if isinstance(component, Folder):
for child in component.children:
if self._would_create_cycle(child):
return True
return False

  1. Uniform Treatment - Clients treat leaves and composites uniformly
  2. No Type Checking - Client doesn’t need to check if object is leaf or composite
  3. Recursive Operations - Operations work naturally on tree structures
  4. Easy to Extend - Add new component types easily
  5. Tree Structures - Natural representation of hierarchies
  6. Simplified Client Code - Client code is simpler and cleaner

Composite Pattern is a structural design pattern that composes objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.

  • Uniform treatment - Treat leaves and composites the same way
  • No type checking - Client doesn’t need to check types
  • Recursive operations - Operations work on tree structures naturally
  • Tree structures - Perfect for hierarchical data
  • Simplified code - Client code is simpler
  1. Define component interface - Common interface for leaves and composites
  2. Implement leaf - Individual object that implements interface
  3. Implement composite - Container that implements interface and contains components
  4. Build tree - Compose objects into tree structure
  5. Treat uniformly - Client treats all components the same way
Component (interface)
├── Leaf (implements Component)
└── Composite (implements Component, contains Components)
  • Component - Common interface for leaves and composites
  • Leaf - Individual object (e.g., File, Employee)
  • Composite - Container object (e.g., Folder, Department)
  • Client - Uses components uniformly
class Component:
def operation(self): pass
class Leaf(Component):
def operation(self):
return "Leaf"
class Composite(Component):
def __init__(self):
self.children = []
def operation(self):
return "Composite"
def add(self, component):
self.children.append(component)

✅ Represent part-whole hierarchies
✅ Want uniform treatment of leaves and composites
✅ Have tree structures
✅ Need recursive operations
✅ Want to simplify client code

❌ Simple flat structure
❌ Different operations for leaves and composites
❌ Performance critical (recursive overhead)
❌ Over-engineering simple cases

  • Composite Pattern = Treats leaves and composites uniformly
  • Component = Common interface
  • Leaf = Individual object
  • Composite = Container with children
  • Benefit = Uniform treatment, recursive operations
  • Use Case = Hierarchical structures (file systems, organizations)
class Component:
def operation(self): pass
class Leaf(Component):
def operation(self):
return "Leaf operation"
class Composite(Component):
def __init__(self):
self.children = []
def operation(self):
# Delegate to children
for child in self.children:
child.operation()
def add(self, component):
self.children.append(component)
  • Composite Pattern treats leaves and composites uniformly
  • It uses a common interface for both
  • Composite delegates operations to children
  • It’s perfect for tree structures
  • It’s about uniformity, not just containment!

What to say:

“Composite Pattern is a structural design pattern that composes objects into tree structures to represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly through a common interface.”

Why it matters:

  • Shows you understand the fundamental purpose
  • Demonstrates knowledge of when to use it
  • Indicates you can explain concepts clearly

Must mention:

  • Part-whole hierarchies - Objects that contain other objects
  • Uniform treatment - Want to treat leaves and composites the same way
  • Tree structures - Hierarchical data
  • Recursive operations - Operations that work on both leaves and composites

Example scenario to give:

“I’d use Composite Pattern when building a file system. Files are leaves, folders are composites. Both implement a FileSystemComponent interface with get_size(). When I call get_size() on a folder, it recursively sums the sizes of its children. The client doesn’t need to know if it’s a file or folder - it just calls get_size() on any component.”

Must discuss:

  • Transparent Composite: All methods in interface, leaves raise exceptions for composite operations
  • Safe Composite: Only leaf operations in interface, composite operations only in composite class
  • Preference: Transparent Composite is preferred for uniformity

Example to give:

“I prefer Transparent Composite because it provides a uniform interface. All components have the same methods. Leaves raise exceptions for composite operations like add(), but the client doesn’t need to check types. Safe Composite requires type checking, which breaks the uniformity.”

Benefits to mention:

  • Uniform treatment - Clients treat leaves and composites the same way
  • No type checking - Client doesn’t need to check types
  • Recursive operations - Operations work naturally on trees
  • Easy to extend - Add new component types easily
  • Simplified code - Client code is simpler

Trade-offs to acknowledge:

  • Complexity - Adds abstraction layer
  • Performance - Recursive operations can be slower
  • Over-engineering risk - Can be overkill for simple cases

Q: “What’s the difference between Composite Pattern and Decorator Pattern?”

A:

“Composite Pattern composes objects into tree structures to represent part-whole hierarchies. Decorator Pattern adds behavior to objects dynamically. Composite is about structure and hierarchy, Decorator is about adding functionality. Composite treats leaves and composites uniformly, Decorator wraps objects to add features.”

Q: “How do you prevent circular references in Composite Pattern?”

A:

“I check if adding a component would create a cycle. Before adding a component to a composite, I traverse up the tree to see if the component already contains the composite. If it does, I raise an exception. I also prevent a composite from adding itself as a child.”

Q: “How does Composite Pattern relate to SOLID principles?”

A:

“Composite Pattern supports the Open/Closed Principle - you can add new component types without modifying existing code. It supports Single Responsibility Principle by separating leaf and composite concerns. It supports Liskov Substitution Principle - leaves and composites can be used interchangeably through the common interface. It also supports Dependency Inversion Principle by depending on the Component abstraction.”

Before your interview, make sure you can:

  • Define Composite Pattern clearly in one sentence
  • Explain when to use it (with examples showing uniform treatment)
  • Describe Transparent vs Safe Composite
  • Implement Composite Pattern from scratch
  • Compare with other structural patterns (Decorator, Bridge)
  • List benefits and trade-offs
  • Identify common mistakes (circular references, no delegation)
  • Give 2-3 real-world examples
  • Connect to SOLID principles
  • Discuss when NOT to use it
  • Explain how to prevent circular references

Remember: Composite Pattern is about treating individual objects and compositions uniformly - perfect for tree structures and hierarchies! 🌳