OOD Fundamentals
OOP Foundations
SOLID Principles
Creational Patterns
Behavioral Patterns
Classic OOD Problems: Part 1
Classic OOD Problems: Part 2
Decorator Pattern
You want to add behavior to an object without changing its class. Inheritance seems like the obvious solution: create a subclass that adds the new behavior. But what if you need to add multiple optional behaviors in different combinations? With two options (milk, sugar), you need four subclasses. With five options, you need thirty-two. With ten, you need over a thousand. The number of classes explodes exponentially.
The Decorator pattern solves this by wrapping objects instead of subclassing them. Each decorator implements the same interface as the object it wraps, adds one piece of behavior, and delegates everything else. You compose behaviors by stacking decorators: each layer adds its contribution and passes the call down.

How It Works
A coffee shop illustrates the pattern perfectly. Every beverage has a cost and a description. Decorators add toppings:
Building a drink with milk and sugar:
Each decorator wraps the previous one. The cost chains through all layers: Sugar asks Milk, Milk asks Espresso, Espresso returns 2.00, Milk adds 0.50, Sugar adds 0.25. The total is 2.75.
The decorator's power comes from a simple rule: every decorator implements the same interface as the object it wraps. This means the client cannot tell whether it is talking to the original object or a decorated version. The decoration is invisible.
The Four Roles
Every decorator implementation has four parts:
- Component interface: the shared interface (Beverage) that both concrete objects and decorators implement
- Concrete component: the base object being decorated (Espresso, Latte)
- Base decorator: an abstract class that holds a reference to a wrapped component and delegates by default
- Concrete decorators, classes that override specific methods to add behavior (MilkDecorator, SugarDecorator)
The base decorator is optional but useful: it provides default delegation so concrete decorators only need to override the methods where they add behavior.
The real advantage of decorators over inheritance is timing. Inheritance adds behavior at compile time: you decide the class hierarchy when you write the code, and it is fixed forever. Decorators add behavior at runtime: you decide what to wrap based on configuration, user input, or system state.
Runtime Composition
Consider a logging system where different environments need different processing:
In development, you might get a plain ConsoleLogger. In production, you get a logger wrapped with timestamps, file writing, and encryption: all from the same code with different configuration. With inheritance, you would need to predefine every possible combination as a separate class.
Removing Decorators
Since decorators are objects wrapping other objects, you can also remove them. A feature flag system might add a monitoring decorator when a feature is enabled and remove it when disabled: all without restarting the application.
This is impossible with inheritance. You cannot remove a parent class from an object at runtime. The class hierarchy is baked into the compiled code.
Factory + Decorator
A common pattern combines the Factory pattern with decorators. The factory decides which decorators to apply based on input, and the client receives a fully decorated object without knowing its structure:
The factory encapsulates the decoration logic. The client calls BeverageFactory.create(order) and receives a Beverage: it does not know or care how many decorators are involved.
The subclass explosion problem is the clearest argument for decorators. But there are deeper reasons why decorators are often better than inheritance for extending behavior.

The Numbers
With inheritance, each combination of features requires its own subclass:
| Options | Subclasses needed |
| 2 (milk, sugar) | 4 |
| 3 (+ whipped cream) | 8 |
| 5 (+ vanilla, caramel) | 32 |
| 10 options | 1,024 |
With decorators, you need exactly N decorator classes for N options. Any combination is built at runtime by stacking.
Beyond the Numbers
The subclass explosion is dramatic, but the real problem with inheritance is rigidity:
Adding a new option: With inheritance, you must create subclasses for every existing combination that includes the new option. With decorators, you write one new class.
Removing an option: With inheritance, you must find and delete every subclass that includes the removed option. With decorators, you remove one class and update the factory.
Changing the base: If the base class (Espresso) changes its cost() calculation, every subclass must be retested. With decorators, only the base class test changes: decorators are independent.
When Inheritance Wins
Decorators are not always better. Inheritance is simpler when:
- The combinations are fixed and few (a Shape hierarchy with Circle, Rectangle, Triangle)
- The behavior extension is tightly coupled to the base class's internals
- The extension needs access to protected members
When an interviewer asks about extending behavior, ask yourself: is this a fixed set of types or a combinable set of options? Fixed types = inheritance. Combinable options = decorator. This one question determines the right pattern.
Stacking is where the decorator pattern shows its full power. Each decorator adds one responsibility, and multiple decorators combine into a processing pipeline. The most famous real-world example is Java's I/O stream library.

Java I/O Streams
Reading a file with buffering in Java looks like this:
FileInputStream reads raw bytes from disk. BufferedInputStream wraps it, adding a memory buffer that reduces the number of expensive disk reads. Both implement InputStream. The client calls stream.read() without knowing whether it is buffered or not.
Need encryption and compression too?
Four layers, each adding one behavior. The data flows through all of them on every read: disk, buffer, decrypt, decompress. The client sees a plain InputStream.
Order Matters
The stacking order defines the processing pipeline. For writing:
Reversing compress and encrypt produces different results. Compressing encrypted data is nearly useless because encrypted data has no patterns for the compression algorithm to exploit. The correct order is compress first, then encrypt.
Practical Guidelines
When stacking decorators, follow these principles:
- Innermost = closest to the data source. File streams, network sockets, and database connections go at the center.
- Transformation decorators go in the middle. Compression, encryption, and formatting modify the data as it flows.
- Observation decorators go on the outside. Logging, metrics, and monitoring wrap everything so they see the final form.
- Performance decorators (buffering) go near the I/O boundary. Buffering is most effective close to the slow resource (disk, network).
The decorator pattern is not just a textbook concept: it is one of the most widely used patterns in production software. Once you know what to look for, you see it everywhere.

Web Middleware
Every modern web framework uses decorators for middleware. In Express.js, Django, Flask, and Spring, each middleware function wraps the next handler:
Each middleware is a decorator on the request pipeline. Authentication wraps logging, which wraps the application handler. A request flows through each layer: if any layer rejects it (like auth returning 401), the chain stops. This is exactly the decorator pattern applied to HTTP processing.
Python's @decorator Syntax
Python has first-class support for decorators with the @ syntax:
@log_calls wraps the add function with logging behavior. The underlying mechanism is identical to the OOP decorator pattern: add is replaced by wrapper, which delegates to the original add after adding behavior.
Where Else You See It
- React higher-order components,
withAuth(withLogging(MyComponent))wraps a React component with authentication and logging - Database connection pooling, A logging wrapper around a connection pool that records query times
- Caching layers, A cache decorator checks the cache before delegating to the real data source
- API rate limiting, A rate-limit decorator wraps an API client, tracking call counts before delegating
In interviews, if the problem involves optional cross-cutting concerns (logging, caching, authentication, rate limiting) that should be composable, the Decorator pattern is almost certainly the right answer. These concerns are independent, stackable, and should not pollute the core business logic.
Practice Problems
Put the Decorator pattern into practice. Start with the coffee shop to build the core wrapping mechanic, then implement text formatting decorators for a different domain, and finish with the I/O streams challenge which tests your understanding of stacking order and bidirectional decoration.
Loading problem...
Loading problem...
Loading problem...