Design Patterns
The named solutions worth knowing, and the discipline of not applying them until the problem shows up.
A Vocabulary, Not A Checklist
The failure mode is treating the catalogue as a list of things to use. Patterns are recognised, not applied — you notice you have written one, or you reach for one because the problem it solves has actually appeared.
Creational Patterns
These control how objects get made — useful when construction itself is the complicated part.
| Pattern | Solves | Reach for it when |
|---|---|---|
| Factory | Choosing an implementation at runtime | The choice depends on configuration or input |
| Builder | Constructors with many optional parameters | You are counting positional arguments |
| Singleton | One shared instance | Rarely — it is global state with a nicer name |
| Dependency injection | Handing collaborators in rather than constructing them | Almost always; it is what makes testing possible |
Structural Patterns
These arrange objects into larger structures without rewriting them.
| Pattern | Solves |
|---|---|
| Adapter | An interface you need does not match the one you have |
| Facade | A subsystem is more complicated than callers need |
| Decorator | Adding behaviour — caching, retries, logging — without touching the original |
| Proxy | Controlling access: lazy loading, permissions, remote calls |
| Composite | Treating a tree of things the same as a single thing |
// decorator: retries wrapped round a client, which knows nothing about them
class RetryingClient {
constructor(inner, attempts = 3) { this.inner = inner; this.attempts = attempts; }
async fetch(id) {
for (let i = 1; i <= this.attempts; i++) {
try { return await this.inner.fetch(id); }
catch (err) { if (i === this.attempts || !isTransient(err)) throw err; }
}
}
}
Behavioural Patterns
| Pattern | Solves |
|---|---|
| Strategy | Swapping an algorithm at runtime — pricing rules, sorting, retry policy |
| Observer | Notifying interested parties without coupling to them |
| State | Behaviour that changes with a lifecycle — order, subscription, document |
| Command | Turning a request into an object you can queue, log or undo |
| Template method | A fixed sequence with steps subclasses fill in |
Strategy and state are the two that most often replace a growing conditional. A function branching on a type code across six methods is usually a strategy or a state machine that has not been written down.
Knowing When Not To
| Signal | Reading |
|---|---|
| The same conditional appears in three places | A strategy or polymorphism is hiding there |
| A class changes for unrelated reasons | It has two responsibilities |
| A test needs six mocks to construct | Too many collaborators — split it |
| A pattern with one implementation | Speculative — delete the abstraction |
| You cannot name the problem it solves | Do not add it |
Interview Questions
What is a design pattern actually for?
Communication first: a shared name for a recurring structure. Its value is that a reviewer understands the shape immediately, not that using more of them makes code better.
Why is singleton criticised?
It is global state with a nicer name — hidden dependencies, awkward lifetimes and tests that interfere with each other. A container-managed single instance achieves the same without the coupling.
When would you use a decorator?
To add cross-cutting behaviour — retries, caching, logging, metrics — round an existing implementation without modifying it or its callers.
Strategy or a conditional?
A conditional is fine until the same branching appears in several places or new cases arrive regularly. At that point strategy makes each case a separate testable unit.
What is the cost of a premature abstraction?
Indirection with no payoff: an interface with one implementation is a layer every reader walks through, and it usually turns out to be the wrong shape when the second case finally arrives.
How do you decide an abstraction is justified?
By pressure from real duplication and real change, not anticipation. Write the direct version, and let the third occurrence tell you what the abstraction should be.