Design Patterns interview question · Most asked
What is the Decorator pattern? How is it different from inheritance?
Summary
Decorator wraps an object in another object with the same interface that adds behaviour before or after delegating. Unlike inheritance, decorators are combined at runtime and stack in any order, so you don't need a subclass for every combination of features.
Longer Answer
Typical uses are cross-cutting concerns: logging, caching, retries, metrics, authorization, compression. LoggingFetcher(CachingFetcher(RetryFetcher(http))) composes three features without changing HttpFetcher. Order matters: logging outside the cache logs every call; inside it, only real fetches.
Java I/O streams are the textbook example; Python's @decorator applies the same idea to functions; HTTP middleware is a decorator chain around a handler.
Code Example
class Http:
def get(self, url):
return f"<{url}>"
class Logged: # decorator: same get(), adds logging
def __init__(self, inner):
self.inner = inner
def get(self, url):
print(f"GET {url}")
return self.inner.get(url)
class Cached: # decorator: same get(), adds caching
def __init__(self, inner):
self.inner, self.cache = inner, {}
def get(self, url):
if url not in self.cache:
self.cache[url] = self.inner.get(url)
return self.cache[url]
client = Cached(Logged(Http())) # logging inside the cache: only real fetches are logged
print(client.get("/a"))
print(client.get("/a"))
interface Client { get(url: string): string; }
class Http implements Client { get(url: string) { return `<${url}>`; } }
class Logged implements Client { // decorator: same get(), adds logging
constructor(private inner: Client) {}
get(url: string) { console.log(`GET ${url}`); return this.inner.get(url); }
}
class Cached implements Client { // decorator: same get(), adds caching
private cache = new Map<string, string>();
constructor(private inner: Client) {}
get(url: string) {
if (!this.cache.has(url)) this.cache.set(url, this.inner.get(url));
return this.cache.get(url)!;
}
}
const client = new Cached(new Logged(new Http())); // logging inside the cache: only real fetches are logged
console.log(client.get("/a"));
console.log(client.get("/a"));
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Client client = new Cached(new Logged(new Http())); // logging inside the cache: only real fetches are logged
System.out.println(client.get("/a"));
System.out.println(client.get("/a"));
}
}
interface Client { String get(String url); }
class Http implements Client { public String get(String url) { return "<" + url + ">"; } }
class Logged implements Client { // decorator: same get(), adds logging
private final Client inner;
Logged(Client inner) { this.inner = inner; }
public String get(String url) { System.out.println("GET " + url); return inner.get(url); }
}
class Cached implements Client { // decorator: same get(), adds caching
private final Client inner;
private final Map<String, String> cache = new HashMap<>();
Cached(Client inner) { this.inner = inner; }
public String get(String url) { return cache.computeIfAbsent(url, inner::get); }
}
<?php
interface Client { public function get(string $url): string; }
class Http implements Client { public function get(string $url): string { return "<$url>"; } }
class Logged implements Client { // decorator: same get(), adds logging
public function __construct(private Client $inner) {}
public function get(string $url): string { echo "GET $url\n"; return $this->inner->get($url); }
}
class Cached implements Client { // decorator: same get(), adds caching
private array $cache = [];
public function __construct(private Client $inner) {}
public function get(string $url): string { return $this->cache[$url] ??= $this->inner->get($url); }
}
$client = new Cached(new Logged(new Http())); // logging inside the cache: only real fetches are logged
$first = $client->get("/a");
echo $first, "\n";
echo $client->get("/a"), "\n";
Key Points
- Same interface, wraps and delegates.
- Adds behaviour without changing the class.
- Stackable at runtime; order matters.
- Java I/O streams, Python decorators, middleware.
Deep dive: Design Patterns, Explained Simply: Decorator
Explanation
What interviewers look for
Same-interface wrapping, runtime stacking, a cross-cutting example, and awareness that order matters.
Follow-up questions
- How is a Decorator different from a Proxy?
- What problems do deep decorator stacks cause?
More most-asked Design Patterns questions
- What problem does the Builder pattern solve? Show an example.
- Explain the SOLID principles.
- What is a design pattern, and why are they useful?
- What is the difference between dependency inversion, dependency injection and inversion of control?
- Why is composition often preferred over inheritance?
- What is dependency injection, and what forms does it take?
- Explain the Factory Method pattern and how it differs from a simple factory.
- How do you implement a Singleton, and why is it often considered an anti-pattern?