skip to content

Pattern Selection

Going from a problem to the right pattern, weighing coupling, extensibility and added complexity, and noticing where several patterns overlap or combine. This is the skill an interviewer is testing when they describe a scenario instead of naming a pattern.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

5

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?

level: juniorimportance: must knowfreq 62%

answer

  1. intent over structure
  2. name the forces first
  3. consequences section is the trade
  4. rule of three, refactor to the pattern
  5. "no pattern" is a valid answer

basics

~20 s

Start 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 s

A 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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.

context

open as a page

Strategy, State, and Command all end up as an object holding behaviour behind a small interface. Given that near-identical structure, how do you decide which one a problem calls for?

level: middleimportance: must knowfreq 58%

basics

~20 s

By intent, not shape. Strategy swaps interchangeable algorithms chosen from outside. State encapsulates behaviour that changes with an object's lifecycle stage, and states decide the next state. Command turns a request into an object you can queue, log, retry, or undo.

open as a page

How do you make the cost/benefit case for introducing a design pattern — what concrete trade-offs (coupling, extensibility, complexity) do you weigh, and how do you decide it is not worth it?

level: seniorimportance: must knowfreq 48%

basics

~20 s

A pattern buys cheap change along one axis and charges indirection everywhere. Weigh how likely that change is, how many new types and hops you add, whether coupling direction improves, and whether tests get simpler. If you cannot name the change it makes cheap, skip it.

open as a page

Adapter, Decorator, Proxy, and Facade all wrap one object (or several) behind another object. What distinguishes them, and how do you pick between them when you are about to introduce a wrapper?

level: middleimportance: should knowfreq 50%

basics

~20 s

By what the wrapper does to the interface and the behaviour. Adapter changes the interface to one the client expects. Decorator keeps the same interface and adds behaviour, stackably. Proxy keeps the same interface and controls access (lazy, remote, caching, permission). Facade offers a simpler interface over a whole subsystem.

open as a page

Patterns rarely appear alone. How do you reason about combining patterns in one design — which combinations are natural, which signal a modelling mistake, and how do you keep a large codebase's pattern usage coherent?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Combine patterns when each answers a different question — for example a factory choosing a strategy, or a composite of commands. It is a warning sign when several patterns solve the same question, when wiring code outweighs domain code, or when nobody can describe the runtime object graph.

open as a page