Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

Decorator Pattern

Add behavior dynamically - wrap objects to extend functionality without modifying them!

Decorator Pattern: Adding Behavior Dynamically

Section titled “Decorator Pattern: Adding Behavior Dynamically”

Now let’s explore the Decorator Pattern - a powerful structural design pattern that lets you add behavior to objects dynamically.

Imagine you’re ordering a coffee. You start with a basic coffee, then add milk, then add sugar, then add whipped cream. Each addition modifies your coffee without changing the base coffee itself. The Decorator Pattern works the same way - you wrap objects with decorators to add functionality dynamically!

The Decorator Pattern attaches additional responsibilities to an object dynamically. It provides a flexible alternative to subclassing for extending functionality.

The Decorator Pattern is useful when:

  1. You want to add behavior dynamically - At runtime, not compile time
  2. You want to add behavior to individual objects - Not entire classes
  3. You want to avoid subclass explosion - Too many combinations to subclass
  4. You want flexible extension - Add features without modifying existing code
  5. You want to combine features - Multiple decorators can be combined

What Happens If We Don’t Use Decorator Pattern?

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

Without the Decorator Pattern, you might:

  • Subclass explosion - Need a class for every combination (Coffee, CoffeeWithMilk, CoffeeWithSugar, CoffeeWithMilkAndSugar, etc.)
  • Modify existing code - Need to change base classes to add features
  • Rigid design - Can’t add features at runtime
  • Violate Open/Closed Principle - Need to modify code to extend functionality
  • Code duplication - Similar code repeated across subclasses

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

Diagram

Here’s how the Decorator Pattern works in practice - showing how decorators wrap components:

sequenceDiagram
    participant Client
    participant MilkDecorator
    participant SugarDecorator
    participant SimpleCoffee as SimpleCoffee
    
    Client->>MilkDecorator: get_cost()
    activate MilkDecorator
    MilkDecorator->>SugarDecorator: get_cost()
    activate SugarDecorator
    SugarDecorator->>SimpleCoffee: get_cost()
    activate SimpleCoffee
    SimpleCoffee-->>SugarDecorator: 5.0
    deactivate SimpleCoffee
    SugarDecorator->>SugarDecorator: Add sugar cost: 0.5
    SugarDecorator-->>MilkDecorator: 5.5
    deactivate SugarDecorator
    MilkDecorator->>MilkDecorator: Add milk cost: 1.0
    MilkDecorator-->>Client: 6.5
    deactivate MilkDecorator
    
    Note over Client,SimpleCoffee: Decorators wrap components<br/>and add functionality!

You’re building a coffee shop system. You have different types of coffee and different add-ons (milk, sugar, whipped cream). Without Decorator Pattern:

Problems:

  • Subclass explosion - Need a class for every combination (2^n classes for n features)
  • Hard to extend - Adding new feature requires many new classes
  • Violates Open/Closed Principle - Need to modify code to extend functionality
  • Can’t combine features dynamically - Combinations are fixed at compile time
classDiagram
    class Coffee {
        <<interface>>
        +get_cost() float
        +get_description() string
    }
    class SimpleCoffee {
        +get_cost() float
        +get_description() string
    }
    class CoffeeDecorator {
        <<abstract>>
        -coffee: Coffee
        +get_cost() float
        +get_description() string
    }
    class MilkDecorator {
        +get_cost() float
        +get_description() string
    }
    class SugarDecorator {
        +get_cost() float
        +get_description() string
    }
    
    Coffee <|.. SimpleCoffee : implements
    Coffee <|.. CoffeeDecorator : implements
    CoffeeDecorator --> Coffee : wraps
    CoffeeDecorator <|-- MilkDecorator : extends
    CoffeeDecorator <|-- SugarDecorator : extends
    
    note for Coffee "Component interface"
    note for SimpleCoffee "Concrete component"
    note for CoffeeDecorator "Base decorator"
    note for MilkDecorator "Concrete decorator"

Real-World Software Example: Text Formatting System

Section titled “Real-World Software Example: Text Formatting System”

Now let’s see a realistic software example - a text editor that needs to apply different formatting (bold, italic, underline) to text dynamically.

You’re building a text editor. You need to apply formatting (bold, italic, underline) to text. Without Decorator Pattern:

Problems:

  • Subclass explosion - Need a class for every combination
  • Hard to extend - Adding new format requires many new classes
  • Can’t combine dynamically - Combinations are fixed at compile time

There are different ways to implement the Decorator Pattern:

Decorator implements the same interface as component, client doesn’t know it’s decorated:

Pros: Client doesn’t need to know about decorators, uniform interface
Cons: Can’t access decorator-specific methods

Decorator adds new methods, client needs to know it’s decorated:

Pros: Can add decorator-specific functionality
Cons: Client needs to know about decorators, breaks transparency


Use Decorator Pattern when:

You want to add behavior dynamically - At runtime, not compile time
You want to add behavior to individual objects - Not entire classes
You want to avoid subclass explosion - Too many combinations to subclass
You want flexible extension - Add features without modifying existing code
You want to combine features - Multiple decorators can be combined

Don’t use Decorator Pattern when:

Simple inheritance works - If you only have a few combinations, inheritance might be simpler
Performance critical - Decorator adds indirection (usually negligible)
Over-engineering - Don’t add complexity for simple cases
Feature order matters - If decorator order affects behavior significantly


Mistake 2: Not Delegating to Wrapped Component

Section titled “Mistake 2: Not Delegating to Wrapped Component”

Mistake 3: Modifying Component Instead of Decorating

Section titled “Mistake 3: Modifying Component Instead of Decorating”

  1. No Subclass Explosion - 1 base + N decorators = 1 + N classes (not 2^N)
  2. Dynamic Combination - Can combine decorators at runtime
  3. Flexible Extension - Add new decorators without modifying existing code
  4. Open/Closed Principle - Open for extension, closed for modification
  5. Single Responsibility - Each decorator adds one feature
  6. Runtime Behavior - Can add/remove features at runtime

Decorator Pattern is a structural design pattern that attaches additional responsibilities to an object dynamically. It provides a flexible alternative to subclassing for extending functionality.

  • Avoid subclass explosion - 1 base + N decorators = 1 + N classes (not 2^N)
  • Dynamic combination - Can combine decorators at runtime
  • Flexible extension - Add features without modifying code
  • Open/Closed Principle - Open for extension, closed for modification
  • Runtime behavior - Add/remove features at runtime
  1. Define component interface - What the component should do
  2. Implement concrete component - Base object
  3. Create base decorator - Wraps component, implements same interface
  4. Implement concrete decorators - Each adds one feature
  5. Wrap dynamically - Combine decorators at runtime
Component (interface)
├── ConcreteComponent (implements Component)
└── Decorator (implements Component, wraps Component)
├── ConcreteDecorator1 (extends Decorator)
└── ConcreteDecorator2 (extends Decorator)
  • Component - Interface for objects that can be decorated
  • ConcreteComponent - Base object
  • Decorator - Base decorator that wraps component
  • ConcreteDecorator - Specific decorator that adds one feature
  • Client - Uses decorated components
class Component:
def operation(self): pass
class ConcreteComponent(Component):
def operation(self):
return "Component"
class Decorator(Component):
def __init__(self, component: Component):
self.component = component
def operation(self):
return self.component.operation()
class ConcreteDecorator(Decorator):
def operation(self):
return f"Decorated({self.component.operation()})"

✅ Add behavior dynamically
✅ Avoid subclass explosion
✅ Combine features flexibly
✅ Extend without modifying code
✅ Add features at runtime

❌ Simple inheritance works
❌ Performance critical (adds indirection)
❌ Over-engineering simple cases
❌ Feature order significantly affects behavior

  • Decorator Pattern = Adds behavior dynamically by wrapping
  • Component = Interface for objects
  • Decorator = Wraps component, adds behavior
  • Benefit = No subclass explosion, dynamic combination
  • Use Case = Many feature combinations (coffee add-ons, text formatting)
class Component:
def operation(self): pass
class ConcreteComponent(Component):
def operation(self):
return "Component"
class Decorator(Component):
def __init__(self, component: Component):
self.component = component
def operation(self):
return self.component.operation()
class ConcreteDecorator(Decorator):
def operation(self):
result = self.component.operation()
return f"Enhanced({result})"
  • Decorator Pattern wraps components to add behavior
  • It uses same interface as component
  • Decorators can be combined dynamically
  • It prevents subclass explosion (2^n → 1+n)
  • It’s about enhancement, not compatibility!

What to say:

“Decorator Pattern is a structural design pattern that attaches additional responsibilities to an object dynamically. It wraps objects with decorators to add functionality without modifying the base object, providing a flexible alternative to subclassing.”

Why it matters:

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

Must mention:

  • Avoid subclass explosion - Too many combinations to subclass (2^n problem)
  • Dynamic behavior - Want to add features at runtime
  • Flexible combination - Want to combine features dynamically
  • Extend without modifying - Don’t want to modify existing code

Example scenario to give:

“I’d use Decorator Pattern when building a coffee shop system. I have a base coffee and 5 add-ons (milk, sugar, whipped cream, caramel, chocolate). Without Decorator, I’d need 2^5 = 32 subclasses for all combinations. With Decorator, I need 1 base + 5 decorators = 6 classes, and I can combine them dynamically at runtime.”

Must discuss:

  • Decorator: Adds behavior, same interface, enhances functionality
  • Adapter: Makes compatible, different interface, about compatibility
  • Key difference: Decorator enhances, Adapter adapts

Example to give:

“Decorator Pattern wraps objects to add behavior while keeping the same interface. Adapter Pattern wraps objects to make incompatible interfaces compatible. Decorator is about enhancement, Adapter is about compatibility. Decorator adds features, Adapter changes interface.”

Benefits to mention:

Trade-offs to acknowledge:

  • Complexity - Adds abstraction layer
  • Performance - Multiple wrappers add indirection
  • Over-engineering risk - Can be overkill for simple cases
  • Order dependency - Decorator order can matter

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

A:

Inheritance creates a static relationship at compile time - you can’t change behavior at runtime. Decorator Pattern creates a dynamic relationship at runtime - you can wrap objects with decorators to add behavior dynamically. Inheritance requires a class for each combination, Decorator allows runtime combination.”

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

A:

“Decorator Pattern supports the Open/Closed Principle - you can extend functionality (add new decorators) without modifying existing code. It supports Single Responsibility Principle - each decorator adds one feature. It supports Dependency Inversion Principle by depending on the Component abstraction rather than concrete implementations.”

Q: “Can decorators be removed after being added?”

A:

“Yes, decorators can be removed, but it requires careful design. You’d need to maintain references to the original component or use a different structure. However, the typical use case is to build up decorators rather than remove them. If you need frequent add/remove, consider a different pattern like Strategy.”

Before your interview, make sure you can:

  • Define Decorator Pattern clearly in one sentence
  • Explain when to use it (with examples showing subclass explosion)
  • Describe Decorator vs Adapter Pattern
  • Implement Decorator Pattern from scratch
  • Compare with other structural patterns (Adapter, Composite)
  • List benefits and trade-offs
  • Identify common mistakes (not delegating, modifying component)
  • Give 2-3 real-world examples
  • Connect to SOLID principles
  • Discuss when NOT to use it
  • Explain how decorators can be combined

Remember: Decorator Pattern is about adding behavior dynamically - wrap objects to enhance functionality without subclass explosion!