← Back to principles

Design Principle

Decorator Pattern: Principles, Trade-offs & Examples

Learn the Decorator pattern: attach responsibilities to objects dynamically. A flexible alternative to subclassing for extending functionality at runtime.

Decorator Pattern

Why This Matters

Think of the Decorator pattern like adding toppings to a pizza. You start with a base pizza, then add toppings (cheese, pepperoni, mushrooms) one at a time. Each topping adds something new without changing the base. The Decorator pattern does the same for code—it lets you add behavior to objects dynamically, one feature at a time.

This matters because sometimes you need to add features to objects, but subclassing creates a combinatorial explosion. If you have 3 base classes and 5 features, you'd need 3 × 2^5 = 96 subclasses. The Decorator pattern lets you compose features dynamically, avoiding this explosion.

In interviews, when someone asks "How would you add features to objects without subclassing?", they're testing whether you understand the Decorator pattern. Do you know how to compose decorators? Can you add behavior dynamically? Most engineers can't. They use inheritance and wonder why they have so many classes.

What Engineers Usually Get Wrong

Engineers often think "Decorator pattern is just composition." But Decorator pattern is more specific—it maintains the same interface as the component it decorates, and decorators can be composed. This allows you to stack decorators (e.g., add compression, then encryption, then logging) without the client knowing.

Engineers also don't understand when to use Decorator vs Strategy. Decorator adds behavior to objects (like adding features). Strategy chooses between algorithms (like choosing a sorting algorithm). Use Decorator when you need to add features dynamically. Use Strategy when you need to choose algorithms.

How This Breaks Systems in the Real World

A service was using inheritance to add features. They had classes like CompressedFile, EncryptedFile, CompressedEncryptedFile, LoggedFile, CompressedLoggedFile, etc. The class hierarchy exploded. Adding a new feature required creating many new classes. The fix? Use Decorator pattern. Create decorators for each feature (CompressionDecorator, EncryptionDecorator, LoggingDecorator). Compose them dynamically. Now adding a new feature is just creating one decorator, not many classes.

Another story: A service was using decorators but didn't maintain the same interface. Each decorator had a different interface, making composition impossible. The fix? Ensure decorators implement the same interface as the component they decorate. This allows decorators to be composed.


What is the Decorator Pattern?

Decorator Pattern provides:

  • Dynamic behavior: Add behavior at runtime
  • Composition over inheritance: Extend without subclassing
  • Flexible combination: Combine multiple decorators
  • Single responsibility: Each decorator adds one feature

Use cases:

  • Adding features to objects dynamically
  • Extending functionality without modifying classes
  • Combining multiple features
  • Stream processing (Java I/O streams)

Structure

Component (interface)
  └─ operation()

ConcreteComponent (implements Component)
  └─ operation()

Decorator (implements Component)
  └─ component: Component
  └─ operation() (calls component.operation())

ConcreteDecorator (extends Decorator)
  └─ operation() (adds behavior, calls super.operation())

Examples

Basic Decorator Pattern

TypeScript
Python
Java
1

HTTP Request Decorator

TypeScript
Python
Java
1

Common Pitfalls

  • Too many decorators: Can become complex. Fix: Keep decorators simple, limit nesting
  • Order matters: Decorator order affects behavior. Fix: Document order, use builder pattern
  • Performance overhead: Multiple layers add overhead. Fix: Consider performance impact
  • Not using interfaces: Tight coupling. Fix: Use interfaces, dependency injection

Interview Questions

Beginner

Q: What is the Decorator pattern and how does it differ from inheritance?

A:

Decorator Pattern attaches additional responsibilities to objects dynamically.

Key characteristics:

  • Dynamic behavior: Add behavior at runtime
  • Composition: Uses composition instead of inheritance
  • Flexible combination: Combine multiple decorators
  • Single responsibility: Each decorator adds one feature

Example:

TypeScript
Python
Java
1

Difference from inheritance:

  • Inheritance: Static, compile-time, creates new class
  • Decorator: Dynamic, runtime, wraps existing object

Benefits:

  • Flexibility: Add/remove features at runtime
  • No class explosion: Don't need classes for every combination
  • Single responsibility: Each decorator does one thing

Intermediate

Q: Explain how the Decorator pattern works. How do you compose multiple decorators?

A:

Decorator Pattern Structure:

1. Component Interface:

TypeScript
Python
Java
1

2. Concrete Component:

TypeScript
Python
Java
1

3. Decorator (Abstract):

TypeScript
Python
Java
1

4. Concrete Decorators:

TypeScript
Python
Java
1

Composing Multiple Decorators:

TypeScript
Python
Java
1

Order matters: Decorators are applied in reverse order of wrapping.


Senior

Q: Design a decorator system for a text processing pipeline that supports multiple transformations (encryption, compression, formatting) that can be applied in any order. Handle performance and ensure transformations are reversible.

A:

TypeScript
Python
Java
1

Features:

  1. Reversible transformations: Each decorator can reverse
  2. Flexible composition: Add decorators in any order
  3. Performance: Can optimize based on decorator order
  4. Pipeline builder: Fluent interface for building pipelines

Keep exploring

Principles work best in chorus. Pair this lesson with another concept and observe how your architecture conversations change.