Skip to content
intermediate Phase 3 · Design Patterns at Scale

Pattern Selection and Its Trade-offs

Decide when a pattern earns its indirection and when plain code wins.

50m
0 problems
Topic Progress 0%

The Invoice Nobody Can Trace: Counting a Pattern's Cognitive Load

A support engineer investigates why a customer was billed 47.00 instead of 44.10. In the patterned codebase, the answer crosses eight hops: PricingEngine → strategy registry → VipPricing → discount combiner → coupon decorator → rounding policy → currency formatter → audit listener that finally logs the applied rule names. Every hop is defensible in isolation; the round trip is not. Every pattern introduces indirection that newcomers must learn before contributing, and that cost is the tax this topic teaches you to weigh against the pattern's benefits.

The tax has three measurable components:

Cost component What it looks like How to measure it
Navigation answer to a simple question spans N classes class count on the trace path
Vocabulary readers must know registry, decorator, combiner onboarding time to first shipped feature
Abstraction surface every interface is a thing to learn and misuse interface count per feature shipped

A useful heuristic for architecture reviews: feature-to-class ratio in the hot path. A checkout flow implemented in nine classes where six are pattern scaffolding (interfaces, registries, combiners) has spent more structure than behavior; a flow with the same nine classes where seven carry domain meaning (Money, Cart, Reservation, FraudScore) has spent it well. The distinguishing question is not how many classes exist but how many of them a reader must hold in mind to answer 'what happened to this order?' — the debugging path is the real reader of architecture, and it is the reader your design is either serving or obstructing.

The benefits side of the ledger is equally concrete, which is how the trade becomes an arithmetic rather than a debate. A strategy registry buys config-driven selection; a decorator stack buys behavior addition without editing stable code; an adapter buys vendor isolation worth a one-file upgrade. When each benefit names the change it prevents, you can price the pair: eight hops of navigation against a vendor upgrade that would have cost three teams a release. That trade is easy. The same eight hops against a single anticipated variation is not — and the failure mode is common enough to have a name, the speculative generality smell, where frameworks serve exactly one caller.

Interviewers probe this balance directly: 'tell me about a time you removed a pattern' scores as well as 'a time you added one', because both demonstrate the same skill — holding the pattern's vocabulary lightly and the system's readers firmly in mind. The concrete cases where plain code wins, and how to argue it in a room that reveres patterns, follow next: When Plain Code Wins. There is a second reader the tax reaches beyond the newcomer: the incident responder at 3 a.m. Navigation cost compounds under stress — the reader who must trace a charge during a billing outage has less working memory for vocabulary than the engineer who designed the flow on a calm afternoon, and every interface between the entry point and the answer is a place the responder's hypothesis can stall. Several organizations now run a traceability drill as part of architecture review: hand the design to an engineer who did not build it and time how long the first end-to-end trace takes. Twenty minutes is a structure signal; two hours is a redesign argument, and the number is hard to argue with in the room that funded the pattern.

When Plain Code Wins: The Conditional That Earned Its Keep

Two worked cases carry this section, both from codebases that had patterns and removed them — the direction of travel interviewers find most interesting.

Case one: the strategy pattern with two strategies and a stable contract. A shipping-cost module had ShippingStrategy, an interface with one method, two implementations (flat rate, weight-based), a registry map, and a selector reading config. Total scaffolding: four classes and a config key to express what one well-named function per rule would express directly. The refactor collapsed it to:

static Money shippingCost(Cart cart, Zone zone) {
    if (cart.isDigital()) return Money.ZERO;
    return cart.totalWeight().above(KILO_20)
        ? flatPerKilo(cart, zone)
        : flatRate(zone);              // one decision, readable whole
}

The criteria that justified it: exactly two variants, no runtime selection beyond one config flag, no variant reused by another flow, and the entire decision legible in a single screen. Prefer removing conditional branches over adding a class hierarchy for a single variation — the hierarchy's payback never arrived because the third strategy the team kept predicting never shipped in three years. The honest counterpoint: if pricing experiments had been frequent and per-tenant, the registry would have been earning its keep; the deciding evidence was the change history, not taste.

Case two: the observer infrastructure around a projection. A service had a three-listener in-process observer mechanism plus an event bus for exactly one listener — a read-model projector that could tolerate losing updates because it was rebuildable from source data. The indirection (event classes, listener interface, wiring, failure-handling policy debate) served a single consumer whose true requirement was a periodic rebuild. Replacing the whole apparatus with a scheduled projection job removed a failure mode (listener drift between the write model and the projection) and made the rebuild path the primary path, where it was observable and testable. One listener, no independent evolution needed, no fan-out — the observer's entire value proposition was absent.

The generalization behind both cases: patterns solve recurring problems, so applying them to one-off designs adds useless layers — and the recurring part is empirical, visible in the change history you already measure for smell triage. The extraction heuristic from the SOLID phase completes the lifecycle here: add the seam on the second observed variation, extract the pattern on the third, when a real common interface is visible from examples rather than imagined from one. Between second and third sits the pattern's proving ground, and code that survives it earns its structure; code that never sees the third variation should return to plain form without ceremony. Patterns are a compressible resource: the skill is knowing when to spend and when to keep the change simple. A third case worth carrying, because it is the most common in review: the factory hierarchy wrapping construction that has no decisions in it. One concrete class, constructor arguments passed straight through, no selection, no validation, no shared lifetime — the factory adds a class and a hop while hiding nothing, since the caller already knew exactly what it wanted. The test that decides such cases is the same one from the creational material: name the construction decision the wrapper absorbs. If the answer is a blank stare, the wrapper is ceremony, and deleting it makes the call path one hop shorter for every future reader and responder.

The Four-Question Review: How Pattern Proposals Get Accepted, Deferred, or Rejected

Pattern debates in design review degenerate quickly — authority substitutes for analysis, or the most novel pattern wins by intrigue. A fixed four-question checklist turns the discussion into arithmetic any reviewer can follow, and it works in both directions: it pressures weak pattern proposals and it pressures reflexive plain-code conservatism when a pattern genuinely fits.

## Pattern review — answer all four or the proposal waits

1. WHAT recurring problem does this name? (not: what pattern is this)
   'Vendor SDK churn touches 40 files' — concrete, current, measurable.
2. WHAT does it cost? Class count on the debugging path, vocabulary
   for newcomers, interfaces to maintain.
3. WHAT simpler alternative was considered, and what evidence
   rules it out? (if/else, a function parameter, one module boundary)
4. WHAT would make us remove it later? (the third strategy never
   arrives; the vendor is sunset; name the review date)

Question one forces the problem into focus before the solution: proposals framed as 'let's use the visitor pattern' are reframed as 'we have 12 node types and the operations over them grow monthly while the type set is stable', which is the visitor's actual precondition — and may also reveal it is not met. Question two is where the indirection ledger from the first chapter gets priced in the room, with the class-count and vocabulary arguments made explicit. Question three is the egalitarian move: it makes plain alternatives a required part of the analysis, so the burden of proof sits symmetrically on both camps, and 'because patterns are good architecture' fails the question while 'the if-chain has eleven branches and three teams edit it' passes it. Question four plants the removal path, converting an apparent permanent decision into a monitored one — the same reversibility discipline the decision-records material taught, applied to structure.

Outcomes land in three buckets, and writing the bucket down is the deliverable. Accepted: all four answers recorded in the ADR, review date set. Deferred: the problem is real but below the threshold — a note in the ADR log naming the trigger (the third variation, the fifth branch, the next vendor contract) so the decision re-presents itself automatically rather than by memory. Rejected: the simpler alternative wins, recorded with its reason, so the same proposal does not return unchanged in three months and so future readers know the trade was considered rather than missed.

For interview preparation, this checklist doubles as the answer skeleton for the family of questions about patterns — 'when would you not use X', 'have you removed patterns', 'how do you decide'. Each answer names the recurring problem, prices the indirection, cites the alternative considered, and states the removal condition. That structure reads as engineering judgment because it is one: patterns are tools selected by need and evidence, held lightly enough to be removed when the need ends. With the pattern lifecycle closed, the roadmap returns to structure at larger scale: Layered Architecture. One timing note for the deferred bucket: triggers should be observable by someone other than the original proposer, or they silently expire with the team that wrote them. 'The third strategy' is visible in the code, 'fifth branch' is visible in the diff log, 'next vendor contract' is visible in procurement — each can be spotted by whoever is on the team next quarter. A trigger that lives only in the original architect's memory is a rejection that merely postpones its admission, which is why the ADR log gets a quarterly skim alongside the risk register rather than being archived as history.

Quiz

1. A pricing module spans eight classes from entry point to the final audit log, and a support engineer cannot trace a single charge without opening all eight. What is the architect's correct read?

Question 1 options

2. A shipping module has ShippingStrategy with two implementations, a registry, and a config selector; the third predicted variant has not shipped in three years. What does the evidence justify?

Question 2 options

3. In the four-question pattern review, what does question three — 'what simpler alternative was considered?' — accomplish?

Question 3 options

4. A proposal is deferred with the note: 'trigger for reconsideration: the third shipping strategy or the fifth branch in the cost if-chain.' What decision-record discipline does this demonstrate?

Question 4 options

Flashcards

Question

What three components make up a pattern's indirection cost, and how is each measured?

Answer

Navigation (classes on the trace path — class count from entry point to the answer), vocabulary (registry, decorator, combiner to learn — onboarding time to first shipped feature), and abstraction surface (interfaces to learn and misuse — interface count per feature shipped). The debugging path is the real reader of architecture: the question is how many classes a reader must hold to answer 'what happened to this order?'.

Question

State the two plain-code cases and their deciding criteria.

Answer

Strategy with two stable variants, one config flag, no reuse elsewhere, and no third variation in three years → collapse to one conditional function whose whole decision is legible on one screen. Observer with a single rebuildable listener → replace the event apparatus with a scheduled projection job, making the rebuild the primary observable path. Both decided by change-history evidence, not taste: the pattern's benefit requires recurring, independent variation.

Question

What are the four questions of the pattern-review checklist, and what does each accomplish?

Answer

1) What recurring problem does this name? — forces the problem before the solution (and tests the pattern's preconditions, e.g., visitor needs a stable type set with growing operations). 2) What does it cost? — prices navigation, vocabulary, abstraction surface in the room. 3) What simpler alternative was considered, and what evidence rules it out? — makes the burden of proof symmetric. 4) What would make us remove it later, and when do we review? — plants the removal path and converts a structural decision into a monitored one.

Question

What are the three outcomes of a pattern review, and what does each require in writing?

Answer

Accepted: all four answers recorded in the ADR plus a review date. Deferred: the problem is real but below threshold — record the trigger (third variation, fifth branch, next vendor contract) so the decision re-presents itself automatically. Rejected: record the winning simpler alternative and its reason, so the proposal does not return unchanged and future readers see the trade was considered, not missed.

Question

State the pattern-lifecycle heuristic and its grounding.

Answer

Add the extension seam on the second observed variation; extract the full pattern on the third, when a real common interface is visible from examples rather than imagined from one. Grounding: patterns solve recurring problems, so one-off applications add useless layers; the recurrence is empirical, read from the same change-history evidence (churn, commit-theme clustering) used for smell triage. Code that survives the proving ground earns structure; code that never sees the third variation returns to plain form.

Revision Notes

Key Takeaways

  • 1. Every pattern charges navigation, vocabulary, and abstraction-surface tax; the trade is arithmetic when the benefit names the concrete change it prevents, and waste when it serves only an anticipated variation
  • 2. The debugging path is architecture's real reader — measure how many classes a reader must hold to answer 'what happened to this order?' before defending a hop chain
  • 3. Plain code wins on evidence: two stable variants and one flag collapse into one legible function; a single rebuildable listener replaces an event apparatus with a scheduled job
  • 4. The lifecycle heuristic — seam on the second variation, pattern on the third, collapse when the third never arrives — keeps structure proportional to observed change, not imagined futures
  • 5. The four-question checklist makes pattern debates symmetric and falsifiable, producing three recorded outcomes: accepted with a review date, deferred with a trigger, rejected with its reason

Interview Tips

  • • 'Tell me about a pattern you removed' — the shipping-strategy collapse: two variants, three years without a third, the one-screen conditional, and the change-history evidence that decided it
  • • 'When would you not use the visitor pattern?' — state its precondition (stable type set, growing operations) and show you test proposals against preconditions rather than reaching for the pattern by name
  • • 'How do you evaluate a pattern proposal in review?' — walk the four questions in order, and mention that question three cuts both ways against pattern enthusiasm and plain-code conservatism
  • • 'Are patterns overused?' — answer with the ledger (navigation, vocabulary, abstraction surface priced against prevented change) and the lifecycle heuristic; the balanced answer beats both enthusiasm and dismissal

Cheat Sheet

Pattern Selection and Trade-offs

Indirection ledger (price every pattern here):

Cost Measurement
Navigation classes on the debugging trace path
Vocabulary onboarding time to first shipped feature
Abstraction surface interfaces per feature shipped

Benefit side must name the change prevented (vendor upgrade touches 40 files → one adapter file). Benefit naming no concrete change = pay no tax.

Plain-code wins (evidence-based):

  • 2 stable variants + 1 flag + no reuse → one conditional function beats strategy registry
  • 1 rebuildable listener → scheduled job beats observer + bus apparatus

Lifecycle heuristic: seam on 2nd observed variation · pattern on 3rd · collapse back when the 3rd never arrives. Recurrence is read from change history, not predicted.

Four-question review:

1. WHAT recurring problem? (pattern preconditions must be met)
2. WHAT cost?  (nav + vocabulary + abstraction surface)
3. WHAT simpler alternative, and what evidence rules it out?
4. WHAT removes it later?  (trigger + review date)

Outcomes: accepted (ADR + review date) · deferred (named trigger) · rejected (recorded reason).

Interview skeleton for any pattern question: problem → cost priced → alternative considered → removal condition.

Continue Learning