Patterns are names, not a checklist
Creational — Factory, Builder, Singleton
def create_notification(kind: str) -> Notification:
if kind == "email":
return EmailNotification()
if kind == "sms":
return SMSNotification()
raise ValueError(kind)
notif = create_notification("email")
notif.send("Hello")
Notification createNotification(String kind) {
if ("email".equals(kind)) return new EmailNotification();
if ("sms".equals(kind)) return new SMSNotification();
throw new IllegalArgumentException(kind);
}
Notification notif = createNotification("email");
notif.send("Hello");
Structural — Decorator, Facade
source: DataSource = FileDataSource("data.txt")
source = EncryptionDecorator(source)
source = CompressionDecorator(source)
source.write("sensitive info")
# write path: compress → encrypt → file
DataSource source = new FileDataSource("data.txt");
source = new EncryptionDecorator(source);
source = new CompressionDecorator(source);
source.write("sensitive info");
// write path: compress → encrypt → file
Game class in Tic-Tac-Toe is a facade. Almost nobody names it in interviews — and that’s fine. Build clean orchestrators; name Facade only when it helps (e.g. wrapping a messy legacy subsystem).Behavioral — Strategy, Observer, State
class PaymentStrategy(Protocol):
def pay(self, amount: float) -> bool: ...
class ShoppingCart:
def __init__(self):
self._strategy: PaymentStrategy | None = None
def set_payment(self, strategy: PaymentStrategy) -> None:
self._strategy = strategy
def checkout(self, amount: float) -> None:
assert self._strategy is not None
self._strategy.pay(amount)
interface PaymentStrategy {
boolean pay(double amount);
}
class ShoppingCart {
private PaymentStrategy strategy;
void setPayment(PaymentStrategy strategy) {
this.strategy = strategy;
}
void checkout(double amount) {
assert strategy != null;
strategy.pay(amount);
}
}
class Stock:
def __init__(self, symbol: str):
self.symbol = symbol
self._price = 0.0
self._observers: list[Observer] = []
def attach(self, observer: Observer) -> None:
self._observers.append(observer)
def set_price(self, price: float) -> None:
self._price = price
for o in self._observers:
o.update(self.symbol, price)
class Stock {
final String symbol;
private double price = 0.0;
private final List<Observer> observers = new ArrayList<>();
Stock(String symbol) { this.symbol = symbol; }
void attach(Observer observer) {
observers.add(observer);
}
void setPrice(double price) {
this.price = price;
for (Observer o : observers) {
o.update(symbol, price);
}
}
}
# Each state object knows valid actions + next state
machine.select_product() # "insert coin first"
machine.insert_coin() # -> HasCoin
machine.select_product() # -> Dispense
machine.dispense() # -> NoCoin
// Each state object knows valid actions + next state
machine.selectProduct(); // "insert coin first"
machine.insertCoin(); // -> HasCoin
machine.selectProduct(); // -> Dispense
machine.dispense(); // -> NoCoin
Cheat sheet
- Factory — callers shouldn’t pick the concrete class
- Builder — messy / optional construction
- Singleton — truly one global instance (rare)
- Decorator — stack optional runtime behaviors
- Facade — simple entry over complexity
- Strategy — interchangeable behaviors / kill if-else type switches
- Observer — many react to one event
- State — transitions get messy; encapsulate per state
Worked mini-example: Rate limiter Strategy + Factory
Limiter is Strategy; creation from config is Factory; RateLimiter orchestrator doesn’t switch on algorithm mid-allow.
class Limiter(Protocol):
def allow(self) -> bool: ...
def create_limiter(kind: str, cfg) -> Limiter:
if kind == "token_bucket":
return TokenBucket(cfg)
if kind == "sliding_log":
return SlidingLog(cfg)
raise ValueError(kind)
class RateLimiter:
def allow(self, key: str) -> bool:
return self._for(key).allow()
interface Limiter {
boolean allow();
}
Limiter createLimiter(String kind, Config cfg) {
if ("token_bucket".equals(kind)) return new TokenBucket(cfg);
if ("sliding_log".equals(kind)) return new SlidingLog(cfg);
throw new IllegalArgumentException(kind);
}
class RateLimiter {
boolean allow(String key) {
return forKey(key).allow();
}
}
Pattern anti-patterns
- Strategy with one algorithm and no foreseeable second.
- Observer before the subject’s invariants work.
- Decorator stacks that obscure the core verb.
- Singleton everything — hidden global mutable state.
Pattern pick checklist
- Creational — is wiring messy or multi-product?
- Structural — am I adapting an interface or composing behavior?
- Behavioral — does the algorithm, notification, or workflow vary?
- If none apply, draw plain classes. Patterns are optional tools.
Common interview pitfalls
These mistakes show up constantly on this prompt. Name the trap, then show the fix in your design — don’t wait for the interviewer to catch you.
- Catalog dump in minute one.
- Strategy with a single implementation.
- Factory that is just a constructor alias.
- Observer memory leaks / missing unsubscribe narrative.
- Decorator used to hide bad API design.
Interview script (say this)
Read this once out loud before a mock. It’s the spine of a strong answer — not a script to recite robotically.
- I only reach for patterns when something varies.
- Creational: Factory when config picks implementations.
- Structural: Adapter when integrating an awkward API.
- Behavioral: Strategy for swappable algorithms; Observer for fan-out events.
- Rate limiter is my go-to Strategy+Factory example.
- Logger destinations are another clean Strategy/composition example.
- If v1 has one path, I’ll note the seam without implementing three classes.
- Ask me which pattern you want to stress — I’ll wire it into the design.
Extra verification traces
Walk these three traces on the board. If you can narrate them cleanly, your implementation section usually follows.
Staff-level follow-ups
At staff+, they twist the prompt. Answer in one sentence that names the seam — don’t redesign the whole board.
- Too much abstraction? — Inline until second implementation appears.
- DI frameworks? — Manual wiring on whiteboard; mention DI as production concern.
- Pattern vs principle? — Principles guide shape; patterns name recurring shapes.