StudyDSA logoStudyDSA

Command Palette

Search for a command to run...

Sign InSign Up
Sign Up

Data StructuresAlgorithmsBig-O NotationDesign PatternsSystem DesignMachine LearningPhysicsRoboticsAI Research

Factory MethodAbstract FactoryBuilderPrototypeSingletonAdapterBridgeCompositeDecoratorFacadeFlyweightProxyChain of ResponsibilityCommandIteratorMediatorMementoObserverStateStrategyTemplate MethodVisitor

Definition
StudyDSA

Where complexity meets clarity.
By Armas Zarra.

Topics

  • Data Structures
  • Algorithms
  • Big-O Notation
  • Robotics
  • AI Research
  • Machine Learning

Practice

  • Blind 75
  • LeetCode 75
  • NeetCode 150

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Armas Films LLC

Design PatternsDecorator

Decorator

A structural pattern that layers behavior onto one object at runtime by wrapping it in objects that speak its own interface, stacking in any combination.

Definition

Optional behaviors multiply. A coffee with two possible add-ons comes in four configurations, with five it's 32, and a subclass-per-configuration design is dead before the menu finishes growing. Decorator is the Gang of Four structural pattern that adds behavior to an individual object at runtime by wrapping it in another object with the same interface, which the catalog frames as a flexible alternative to subclassing, affecting one instance without touching its class.


A decorator is really just an object wearing the same interface as the thing it holds. Calls arrive at the outermost layer, each wrapper adds its contribution and forwards inward, and the answer assembles itself on the way back out. Because wrapper and wrapped are interchangeable to the type system, wrappers nest in any combination, which is where the multiplication problem goes to die.

A Class Per Combination

Here's the subclassing version of a two-add-on menu, four classes with a hardcoded price each. The arithmetic in the comment deserves a moment of disbelief, so check it yourself before reading on: five optional add-ons means how many classes, and why exactly that number?

The count is 2^5 - 1 = 31 beverage classes, one per non-empty combination, because every add-on independently appears or doesn't. Worse, the prices are precomputed into each class, so milk going up a dime means editing every class with milk in its name, sixteen of the 31. The design has no place where "milk costs 50 cents" is written down once.

Wrap, Add, Repeat

The decorator version stores each add-on's price in exactly one place and assembles orders at runtime. Beverage is the shared interface, Coffee the plain thing being wrapped, and each add-on implements Beverage while holding one, adding its 50 or 20 cents before delegating inward. The example builds Sugar(Milk(Coffee())). Before reading past the code, predict both printed lines:

The cost is 270 and the description reads coffee, milk, sugar. Trace the cost call inward: Sugar asks its inner beverage and plans to add 20, that inner Milk asks its own inner and plans to add 50, the Coffee at the center answers 200, and the additions apply on the way back out: 200 + 50 + 20 = 270. Three classes produced this order, and the same three produce every other combination on the menu without a single new class.

Who Does What

Four roles. The Component (Beverage) is the interface everything speaks. The ConcreteComponent (Coffee) is the real object at the center of any stack. The Decorator (AddOn) is the abstract piece with the pattern's signature move: it implements the Component interface and holds a Component reference at the same time. ConcreteDecorators (Milk, Sugar) override methods to contribute their piece. That implements-and-holds duality is the entire trick, since implementing makes a wrapper usable anywhere, and holding gives it something to forward to.


The arrangement works like winter clothing: a shirt, a sweater over it, a coat over that, each layer adding warmth while keeping the same human shape, so layers stack in whatever combination the weather demands. The analogy has a limit that matters in code: clothing layers barely care about order, but decorators can. Add a 10% tip decorator to the coffee stack and Tip(Milk(order)) tips the milk while Milk(Tip(order)) doesn't, and stream wrappers that buffer and compress behave differently in each order. The pattern makes stacking free, and it makes no promises about stacking being commutative.

Decorator: Step-by-Step

Installing the pattern is four moves:

1. Give Wrappers the Same Voice as the Wrapped

Everything speaks Beverage: the plain coffee and every add-on alike. Sharing the interface is what lets a wrapped object go anywhere an unwrapped one goes, including into another wrapper.

2. Hold the Inner Component by Its Interface

The decorator base (AddOn) stores a Beverage, never a concrete class, so it can't tell whether it's wrapping plain coffee or a stack already three layers deep. That ignorance is what makes stacking work.

3. Add Your Bit, Then Delegate Inward

Milk.cost() is one addition wrapped around one delegation: inner.cost() + 50. Every decorator method follows that shape, contributing its slice and passing the rest of the question down the stack.

4. Stack at Runtime, Per Object

Sugar(Milk(Coffee())) is a decision made while the program runs, for this one order. The next order can stack differently from the same three classes, which is the combinatorial win subclassing could never offer.
The Pattern That Runs java.io

The most-typed decorator stack in software history is Java's I/O library. The line new BufferedInputStream(new GZIPInputStream(new FileInputStream("f.gz"))) is three layers of this page's diagram: Java's own tutorials teach stream wrapping as decoration, and the academic literature names FilterInputStream and its subclasses a classic example of the Decorator pattern. A single read() call traces exactly like the coffee: the buffer asks the gzip layer when it runs dry, the gzip layer inflates compressed bytes it pulls from the file layer, and one decompressed byte rides back up through the stack.


The same library is also the pattern's cautionary tale, which is worth saying in the same breath. Stacks of three or four look-alike wrappers are hard to assemble correctly, impossible to modify in the middle, and confusing to debug when behavior smears across layers. java.io's reputation for ceremony comes less from the pattern being wrong than from the pattern being everywhere, including places a single configured class would have served.

Without Inheritance: Go and Rust

Both languages ship their canonical decorator in the standard library, wrapped around I/O just like Java. Go's bufio.NewReader accepts any io.Reader and returns a buffered object that itself implements io.Reader, the implements-and-holds shape with interface satisfaction standing in for inheritance. Rust's BufReader adds buffering to any Read and implements Read itself, so wrappers chain identically: BufReader::new(GzDecoder::new(file)) reads like the Java line with the wrapping order reversed into constructors.


The code tabs above follow those exact shapes, a struct holding the inner component by interface in Go, a Box<dyn Beverage> in Rust. Neither language needed the abstract Decorator base class, which is a small lesson in what the pattern actually requires: a shared interface and a held reference, with the base class being Java-era packaging.

Python's @decorator Is a Different Thing

You might think Python's @decorator syntax is this pattern built into the language, and the name actively encourages the mistake. Brandon Rhodes's Python patterns guide opens by separating them: in 2003 Python's core developers reused the word for an unrelated feature shipping in Python 2.4. The @ syntax transforms a function at definition time, once, when the module loads. The GoF pattern wraps an object at runtime, per instance, stackable in arbitrary combinations per order. A @lru_cache on a function is a definition-time rewrite, while a Milk around one specific coffee is a runtime decision, and conflating them makes both harder to think about.


The nearer sibling is Proxy, which shares the implements-and-holds shape exactly. Intent separates them: a decorator adds behavior and is handed its component from outside, while a proxy controls access and typically creates or manages its subject itself. Same silhouette, opposite job descriptions, and the distinction matters because reviewers should ask different questions of each.

Best Practices

Stacking is the power and the failure mode, and these five details keep it the former:


A decorator that type-checks its inner component for a specific class has broken the contract that makes stacks composable. Each layer should work over the bare interface, indifferent to what's underneath.

Check Yourself

Two questions before you go. First: the Decorator role both implements Beverage and holds a Beverage. Name what each half buys, and what breaks if either is missing. Second: a teammate calls Python's @functools.lru_cache "the Decorator pattern in the standard library". What's right about that claim, and what's wrong? Both answers are on this page.


Then go spot it in the wild, beyond the java.io stack you've already seen. Logging wrappers, retry wrappers, caching layers, and metrics middlewares around HTTP clients are decorators the moment they implement the same interface they hold. Find one in your own codebase, usually named something like LoggingClient or CachedRepository, and check whether it composes: if you can wrap the wrapper, the pattern is working, and if you can't, one of the five practices above will say why.

the_subclass_explosion.py

class Coffee:
    def cost(self) -> int:
        return 200  # cents

class MilkCoffee:
    def cost(self) -> int:
        return 250

class SugarCoffee:
    def cost(self) -> int:
        return 220

class MilkSugarCoffee:
    def cost(self) -> int:
        return 270

# Two add-ons already cost three extra classes, one per combination.
# Five add-ons cost 31, and repricing milk means hunting down every
# class whose name mentions it.

# Example usage
print(MilkSugarCoffee().cost())  # 270

decorator.py

from abc import ABC, abstractmethod

class Beverage(ABC):  # Component: the interface everything speaks
    @abstractmethod
    def cost(self) -> int: ...

    @abstractmethod
    def description(self) -> str: ...

class Coffee(Beverage):  # ConcreteComponent: the thing being wrapped
    def cost(self) -> int:
        return 200  # cents

    def description(self) -> str:
        return "coffee"

class AddOn(Beverage):  # Decorator: implements Beverage AND holds one
    def __init__(self, inner: Beverage):
        self._inner = inner

class Milk(AddOn):  # ConcreteDecorator
    def cost(self) -> int:
        return self._inner.cost() + 50

    def description(self) -> str:
        return self._inner.description() + ", milk"

class Sugar(AddOn):  # ConcreteDecorator
    def cost(self) -> int:
        return self._inner.cost() + 20

    def description(self) -> str:
        return self._inner.description() + ", sugar"

# Example usage: stack at runtime, per order
order = Sugar(Milk(Coffee()))
print(order.cost())         # 270
print(order.description())  # coffee, milk, sugar