Suggest an editImprove this articleRefine the answer for “decorator pattern”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Decorator pattern** wraps an object to add new behavior at runtime without modifying the original class. ```typescript class MilkDecorator implements Coffee { constructor(private coffee: Coffee) {} cost() { return this.coffee.cost() + 2; } // adds to original description() { return this.coffee.description() + ", Milk"; } } const latte = new MilkDecorator(new SimpleCoffee()); // cost: 7 ``` **Key:** the decorator implements the same interface as the wrapped object, so callers don't know the difference.Shown above the full answer for quick recall.Answer (EN)Image**Decorator pattern** wraps an object to add new behavior at runtime without modifying the original object or creating subclasses. ## Theory ### TL;DR - Analogy: adding toppings to a pizza. Each topping wraps the previous layer; the pizza stays the same underneath. You can combine toppings in any order. - Core difference: inheritance adds behavior at compile-time to the class; decorators add behavior at runtime to individual instances. - Decision rule: use decorators when you need to mix features dynamically, can't modify the original class, or want to avoid subclass explosion. ### Quick example ```typescript // Original object - stays unchanged class Coffee { cost() { return 5; } description() { return "Coffee"; } } // Decorator wraps and extends class MilkDecorator { constructor(private coffee: Coffee) {} cost() { return this.coffee.cost() + 2; } description() { return this.coffee.description() + ", Milk"; } } const coffee = new Coffee(); const withMilk = new MilkDecorator(coffee); console.log(withMilk.cost()); // 7 console.log(withMilk.description()); // "Coffee, Milk" ``` `MilkDecorator` holds a reference to the original `Coffee` object, calls its methods, and adds its own logic on top. The original is untouched. ### Key difference from inheritance Inheritance creates a fixed hierarchy at compile-time. `CoffeeWithMilk extends Coffee` adds milk behavior to every instance of that subclass, permanently. Decorators wrap individual instances at runtime. You can apply `MilkDecorator` to one coffee and `SugarDecorator` to another, using the same classes. This also solves subclass explosion. Without decorators you'd need `CoffeeWithMilkAndSugar`, `CoffeeWithMilkAndCaramel`, `CoffeeWithSugarAndCaramel` as separate classes. Three decorator classes cover every combination. ### When to use - **Third-party code:** you can't modify the original class, so wrap it instead. - **Dynamic combinations:** `MilkDecorator(SugarDecorator(coffee))` vs `SugarDecorator(coffee)` without changing any class definition. - **Cross-cutting concerns:** add logging, caching, or validation without touching core logic. - **Feature flags:** conditionally wrap objects based on config or environment. ### How it works internally The decorator keeps a reference to the wrapped object and implements the same interface. When a method is called on the decorator, it calls the wrapped object's method, adds its own logic before or after, and returns the result. This creates a chain where each layer intercepts calls and passes them down. In JavaScript and TypeScript this happens through object composition at runtime. No class hierarchy changes. You're just passing objects into other objects. ### Common mistakes **1. Not implementing the full interface** The decorator must expose all methods of the wrapped object. Skip one and callers get a runtime error. ```typescript // Wrong - missing methods break callers class LoggingDecorator { constructor(private obj: UserRepository) {} save() { console.log("saving"); return this.obj.save(); } // Missing: delete(), update(), find() } // Right - full interface preserved class LoggingDecorator implements UserRepository { constructor(private obj: UserRepository) {} save() { console.log("saving"); return this.obj.save(); } delete() { console.log("deleting"); return this.obj.delete(); } update() { console.log("updating"); return this.obj.update(); } find() { console.log("finding"); return this.obj.find(); } } ``` **2. Mutating the wrapped object** Adding properties directly to `this.obj` defeats the purpose. The original gets polluted and you can't revert the decoration. ```typescript // Wrong - pollutes the original class CacheDecorator { constructor(private obj: DataService) { (this.obj as any).cache = new Map(); // don't do this } } // Right - decorator owns its own state class CacheDecorator implements DataService { private cache = new Map(); constructor(private obj: DataService) {} getData(key: string) { if (this.cache.has(key)) return this.cache.get(key); const data = this.obj.getData(key); this.cache.set(key, data); return data; } } ``` **3. Ignoring decorator order** Order matters. Wrapping `UppercaseDecorator` around `MetadataDecorator` breaks because `toUpperCase()` receives an object, not a string. In Express, `withCache(withAuth(handler))` means auth runs inside the cache check. Reverse the order and you cache unauthenticated requests. ```typescript // Works: uppercase first, then metadata wraps it const v1 = new MetadataDecorator(new UppercaseDecorator(processor)); // { value: "HELLO", timestamp: 1713110126000 } // Breaks: UppercaseDecorator receives an object, not a string const v2 = new UppercaseDecorator(new MetadataDecorator(processor)); // TypeError: result.toUpperCase is not a function ``` **4. Using decorators when inheritance is the right fit** If behavior belongs on every instance of a class, inheritance is simpler. Decorators are for optional, per-instance behavior. ```typescript // Right: every Dog barks - use inheritance class Animal { move() {} } class Dog extends Animal { bark() {} } // Right: only some dogs are trained - use a decorator const trainedDog = new TrainedDecorator(new Dog()); ``` **5. Deep chains on hot paths** Each decorator layer adds a function call. Five decorators is fine. Fifty on every incoming request is measurable overhead. Profile before assuming decoration is free. ### Real-world usage - **React:** Higher-order components (HOCs) like `withRouter`, `connect` from Redux wrap components to inject props. - **Express/Node.js:** Middleware functions. `withAuth(withLogging(handler))` is a decorator chain on request handlers. - **Python:** The `@decorator` syntax. `@lru_cache`, `@property`, `@staticmethod` are all decorators. - **Java Streams:** `stream().filter().map().collect()` chains decorators on collections. - **TypeScript/JavaScript:** Function composition with Lodash `_.compose()` or Ramda stacks transformations. In my experience, the pattern clicks once you see Express middleware. Every `app.use()` call is a decorator in disguise. ### Follow-up questions **Q:** How does the decorator pattern differ from the Strategy pattern? **A:** Strategy swaps the algorithm inside an object (one strategy at a time). Decorator stacks multiple behaviors on top of what the object already does. Strategy changes *what* the object does; decorator adds *extra* logic around it. **Q:** What happens when you apply multiple decorators to the same object? **A:** Each decorator wraps the previous one, forming a chain. Calls flow through each layer in reverse order (last applied runs first). This is why order matters, especially when decorators have side effects. **Q:** Can you use decorators with stateful objects? **A:** Yes. The decorator wraps the object, not its state. If the wrapped object's state changes, the decorator sees those changes. The decorator's own state (like a cache) is kept separate. **Q:** How do you preserve type safety in TypeScript with decorator chains? **A:** Declare an interface and make each decorator implement it. Or use generics: `class Logger<T extends UserRepository>` ensures TypeScript tracks what methods are available. Without this, you lose autocomplete after the first wrap. **Q:** (Senior) How would you build a decorator that works with both sync and async methods? **A:** Check whether the return value is a `Promise`. If it is, chain `.then()` to intercept the resolved value. If not, modify synchronously. ```typescript class TransformDecorator implements DataProcessor { constructor(private obj: DataProcessor) {} process(...args: any[]) { const result = this.obj.process(...args); if (result instanceof Promise) { return result.then(value => this.transform(value)); } return this.transform(result); } private transform(value: string) { return value.toUpperCase(); } } ``` ## Examples ### Basic: Coffee with toppings The classic example. Each decorator wraps the previous object and both use the same interface, so they're interchangeable from the caller's perspective. ```typescript interface Coffee { cost(): number; description(): string; } class SimpleCoffee implements Coffee { cost() { return 5; } description() { return "Coffee"; } } class MilkDecorator implements Coffee { constructor(private coffee: Coffee) {} cost() { return this.coffee.cost() + 2; } description() { return this.coffee.description() + ", Milk"; } } class SugarDecorator implements Coffee { constructor(private coffee: Coffee) {} cost() { return this.coffee.cost() + 1; } description() { return this.coffee.description() + ", Sugar"; } } const plain = new SimpleCoffee(); const latte = new MilkDecorator(plain); const sweetLatte = new SugarDecorator(latte); console.log(sweetLatte.cost()); // 8 console.log(sweetLatte.description()); // "Coffee, Milk, Sugar" ``` Three classes cover every combination. No `CoffeeWithMilkAndSugar` subclass needed. ### Intermediate: Express middleware as decorators Express middleware is a real-world decorator chain. Each function wraps the next handler and decides whether to pass the call through. ```typescript type Handler = (req: Request, res: Response) => void; function getUser(req: Request, res: Response) { const user = db.findUser(req.params.id); res.json(user); } function withAuth(handler: Handler): Handler { return (req, res) => { if (!req.headers.authorization) { return res.status(401).json({ error: "Unauthorized" }); } return handler(req, res); }; } function withLogging(handler: Handler): Handler { return (req, res) => { console.log(`${req.method} ${req.path}`); return handler(req, res); }; } // Last applied runs first const secureHandler = withLogging(withAuth(getUser)); app.get("/users/:id", secureHandler); // Request flow: logging -> auth check -> original handler ``` `withLogging` wraps `withAuth`, which wraps `getUser`. Each layer handles one concern and doesn't touch the others. ### Advanced: Async-aware decorator A decorator that handles both sync and async return values without the caller knowing the difference. ```typescript interface DataProcessor { process(data: string): string | Promise<string>; } class RawProcessor implements DataProcessor { process(data: string) { return data; } } class UppercaseDecorator implements DataProcessor { constructor(private processor: DataProcessor) {} process(data: string) { const result = this.processor.process(data); // Handle both sync and async transparently if (result instanceof Promise) { return result.then(value => value.toUpperCase()); } return result.toUpperCase(); } } // Works with sync processor const syncDecorated = new UppercaseDecorator(new RawProcessor()); console.log(syncDecorated.process("hello")); // "HELLO" // Works with async processor too class AsyncRawProcessor implements DataProcessor { process(data: string) { return Promise.resolve(data); } } const asyncDecorated = new UppercaseDecorator(new AsyncRawProcessor()); asyncDecorated.process("hello").then(console.log); // "HELLO" ``` The decorator doesn't need to know whether the inner processor is sync or async. It checks at runtime and handles both paths.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.