How should you decide which design pattern to use in a given situation, and why do experienced engineers insist you start from the problem rather than from the pattern catalogue?
answer
- intent over structure
- name the forces first
- consequences section is the trade
- rule of three, refactor to the pattern
- "no pattern" is a valid answer
basics
~20 sStart from the concrete problem: what varies, what must stay stable, what change you expect. Then look for a pattern whose stated intent restates that problem. Choosing a pattern first and bending code to fit it adds complexity without solving anything.
solid answer
~50 sA design pattern is a named, reusable solution to a recurring design problem, described by intent, structure, and consequences. Selection is intent-matching, not shape-matching. A workable procedure: (1) name the forces — what varies now, what is likely to vary, what must stay stable, who depends on whom; (2) state the problem in one sentence ("the algorithm must be swappable at runtime", "clients depend on a class whose interface I cannot change"); (3) shortlist patterns whose intent restates that sentence; (4) compare their consequences — added indirection, number of new types, testability, direction of coupling; (5) apply the smallest one, or none if a parameter, a function, or a plain conditional suffices. Catalogue-first design produces "pattern-itis": ceremony for a problem you do not have, more types to read, and names that promise flexibility nobody needs. Patterns are also legitimately introduced late, by refactoring, once the variation actually shows up.
go deeper
Say that you first describe the problem and what varies, then look for a pattern whose intent matches, and that simple code with no pattern is often the right answer.
Add the selection procedure (forces → one-sentence problem → shortlist by intent → compare consequences) and give a concrete example where you deliberately did not apply a pattern.
Discuss cost of indirection, likely versus imagined axes of change, refactoring into patterns using the rule of three, and how language features subsume several classic patterns.
Frame it as managing option value: each pattern buys flexibility on one axis at a fixed readability cost; talk about establishing team conventions, reviewing for speculative generality, and keeping a shared pattern vocabulary.
## What a design pattern is A **design pattern** is a documented, named solution to a design problem that recurs across many systems. The classic catalogue describes each pattern with four parts: - **Name** — shared vocabulary, so "put a Strategy here" replaces a paragraph of explanation. - **Intent / problem** — the situation and the *forces* (competing pressures) it resolves. - **Structure / participants** — the roles (objects, interfaces, functions) and how they collaborate. - **Consequences** — what you gain *and* what you pay: indirection, extra types, indirection at runtime, harder stack traces, etc. The consequences section is the one people skip and the one selection depends on. A pattern is a **trade**, never a free upgrade. ## Why "problem first" Many patterns share almost the same *structure* — an object holding a reference to another object behind an interface — yet solve completely different problems. Structure therefore cannot identify a pattern; **intent** can. If you select by structure ("this looks like a Decorator"), you will regularly pick a pattern that is shaped right and purposed wrong, and the name will then mislead every future reader. Starting from the catalogue also inverts the economics. Patterns buy **flexibility along one specific axis** and pay in **indirection everywhere**. If the axis of change you bought is not the axis that actually changes, you paid the cost and got none of the benefit. This failure mode has names: *pattern-itis*, *speculative generality*, *architecture astronautics*. ## A concrete selection procedure 1. **Name the forces.** Write down: what varies today; what has changed twice already; what must stay stable (a published interface, an on-disk format); who is allowed to depend on whom. 2. **State the problem in one sentence**, without naming a pattern. Examples: "the sorting rule differs per customer and is chosen at runtime"; "an object's legal operations differ depending on which lifecycle stage it is in"; "I must add auditing to an operation without touching its implementation". 3. **Shortlist by intent.** Read the intent lines of candidate patterns and keep the ones that restate your sentence. Usually two or three survive. 4. **Differentiate by consequences.** Ask: how many new types? Who now knows about whom? Can I unit-test each part in isolation? Does the added indirection hide the control flow at debug time? Does it make a *likely* future change cheap or an *imaginary* one cheap? 5. **Consider the null option.** A parameter, a higher-order function, a lookup table, a small `when`/`switch`, or simple dependency injection often beats a named pattern. "No pattern" is a legitimate outcome of pattern selection. 6. **Prefer arriving late.** The **rule of three** heuristic: the first occurrence you write plainly; the second you note the duplication; on the third you refactor into the pattern. By then you know the real axis of variation, so the abstraction fits. ## Signals that a pattern *is* warranted - The same conditional on a type/mode/flag appears in several places and grows with each new case. - A new variant currently requires editing existing, tested code (violating open/closed). - A dependency points the wrong way — a policy module knows about a low-level detail. - Something must be substitutable for testing, for a second deployment target, or per tenant. - Two teams keep re-explaining the same collaboration; a named pattern would compress the conversation. ## Signals that it is not - "We might need it later" with no concrete second case. - The abstraction has exactly one implementation and no test double. - The pattern was chosen before the requirement was understood. - You cannot state, in one sentence, what change the pattern makes cheap. ## Edge cases - **Frameworks and libraries** legitimately front-load patterns: their variation axis is "unknown external users", which is real from day one. Application code rarely has that excuse. - **Language features can replace patterns.** First-class functions collapse Strategy and Command into a callable; built-in iteration collapses Iterator; module-level singletons or DI containers replace hand-written Singleton. The *problem* remains; the *ceremony* may be unnecessary. Selecting a pattern includes asking whether the language already ships it. - **Naming carries intent.** If you do apply a pattern, put the pattern's role in the type name (`PricingStrategy`, `RetryingClient` as a Decorator). Wrong names are worse than no names.
- If patterns are supposed to arrive by refactoring, how do you avoid a painful rewrite once you finally need one?Keep the pre-pattern code small and well-tested with behavioural tests that do not depend on internal structure. Introducing Strategy, Decorator, or Factory into a well-covered 50-line class is a mechanical, low-risk refactoring; the risk comes from untested code, not from having deferred the pattern.
- Does using a pattern's name in a class name help or hurt?It helps when the pattern is genuinely load-bearing and the reader benefits from the shared vocabulary (`ExportStrategy`, `EventPublisherProxy`). It hurts when it is noise (`UserManagerFactoryImpl`) or when the name is wrong for the intent, because readers will then reason with the wrong mental model.
Choosing a pattern is like choosing a tool at a hardware store. You do not walk in and buy the nicest-looking wrench, then hunt for a bolt to fit. You bring the bolt.
saying these in an interview costs you the question
- Judging patterns by their UML shape rather than their intent — many patterns have identical structure.
- Treating "more patterns" as a proxy for better design or seniority.
- Introducing an interface with a single implementation and no test double, calling it 'extensibility'.
- Believing patterns must be designed up front and cannot be refactored into existence later.
- Ignoring that the host language may already provide the pattern (functions, iteration, DI) so the ceremony is redundant.