How do you judge whether a construct is genuinely an anti-pattern rather than a legitimate pattern used in a context you're unfamiliar with?
answer
- common + negative consequences + better alternative
- name the concrete harm, not the label
- Chesterton's fence: why is it here?
- knobs: lifetime, team size, churn, blast radius
- dispositions: fix / opportunistic / contain / accept
basics
~20 sJudge by consequences in context, not by name. An anti-pattern is a common solution whose costs reliably outweigh its benefits and for which a better alternative exists. If you can't state the concrete harm here and a cheaper alternative, it's just an unfamiliar choice.
solid answer
~50 sThe original definition of an anti-pattern has two parts: a commonly repeated solution that produces clearly negative consequences, *and* a documented, better refactored solution. Both halves matter — without the second you have a complaint, not an anti-pattern. So the test is empirical and contextual: name the specific harm this instance causes here (untestable, unsafe, blocks a known upcoming change, costs incident time), estimate the cost of the alternative, and check the constraints the original author faced (a 200-line script, a hard latency budget, a framework that constructs objects for you). Many labelled anti-patterns are context-dependent: a global singleton is fine for a stateless formatter, service location is legitimate at a plugin boundary, duplication beats a wrong abstraction. Conversely, a construct can be an anti-pattern *at scale* while being reasonable in a small program, because the costs — coordination, testability, change amplification — only appear with team size and lifetime. Argue from consequences and evidence; treating pattern names as verdicts is itself a failure mode.
go deeper
Say that context matters and that you should name the concrete problem the code causes here rather than relying on the label.
Give the three-part definition (common, negative consequences, better alternative) and one worked example of a construct that is fine in one context and harmful in another.
Add the context variables (lifetime, team size, churn, blast radius, constraints), Chesterton's fence, and the fix/contain/accept dispositions with evidence-based prioritization.
Frame it as decision hygiene at organizational scale: standards that state consequences rather than bans, fitness functions that encode the real constraint, prioritizing by frequency × impact, and preventing cargo-cult reviews that cost more than the debt they chase.
## What the term actually means The term **anti-pattern** (popularized by the *AntiPatterns* book, following the Gang of Four's *design patterns*) is defined as: 1. a **commonly used** solution to a recurring problem — it must be popular, otherwise it is just a bad idea nobody has; and 2. one whose **consequences are, on balance, clearly negative**; and 3. for which a **documented better alternative** (a "refactored solution") exists. Point 3 is the part people forget. "I dislike this" is not an anti-pattern. If you cannot name what to do instead — and its cost — you have a preference, not an argument. ## Why context decides Almost every named anti-pattern in design has a defensible instance: | Named smell | Harmful when | Reasonable when | |---|---|---| | Singleton / global access | mutable state, business code fetches it statically, tests need isolation | stateless, immutable helper; composition root; genuine process-wide device | | Service Locator | scattered through domain logic in an application or a reusable library | plugin/late-binding boundary, framework-constructed objects, temporary migration shim | | God object | long-lived, multi-team, high churn, low cohesion | 200-line script; generated code; a thin stateless façade | | Speculative generality | one implementation, imagined requirement, cheap to add later | second implementation exists; needed as a test seam; hard-to-reverse public contract | | Duplication | one concept, copies must change for the same reason | copies that merely look alike and evolve independently | The variables that flip the verdict are consistent: **lifetime** (throwaway vs decade), **team size** (one author vs five teams), **change rate** (frozen vs weekly), **blast radius** (internal vs published contract), and **constraints** (latency budget, platform limits, framework instantiation rules). ## A practical evaluation procedure 1. **Describe the harm concretely.** Not "it's a god object", but "any change to pricing forces a merge with three teams and we cannot unit-test it without a database". If you cannot make it concrete, stop. 2. **Check the constraints the author faced.** Framework instantiation, a hot loop where indirection cost real latency, an interop requirement, a deadline. Chesterton's fence: understand why it is there before removing it. 3. **Name the alternative and its cost.** Migration effort, risk, retraining, runtime cost. An anti-pattern claim without an alternative is not actionable. 4. **Weigh by frequency × impact.** Hot, complex, frequently-changed code repays cleanup; cold code usually does not, however ugly. 5. **Prefer evidence to aesthetics.** Churn data, conflict rates, defect density, incident postmortems, benchmark numbers — these turn a style argument into a decision. 6. **Decide the disposition**: fix now, fix opportunistically when next touched, contain (wrap behind an interface and stop growing it), or accept and document with an explicit rationale so the next reader does not re-litigate it. ## The meta anti-patterns to avoid in yourself - **Cargo-cult labelling** — applying a name from a book without checking whether the listed consequences actually occur here. - **Golden hammer** — insisting every problem fits your favorite pattern; the mirror image of anti-pattern hunting. - **Pattern-happy design** — adding factories, visitors and mediators to demonstrate knowledge; unnecessary indirection is itself a smell. - **Rewrite reflex** — deciding the label justifies the rewrite, when incremental strangling is cheaper and safer. - **Ignoring the sample size** — one bad experience with a construct is not a repeatable negative consequence. ## How to communicate the judgment In a review or design discussion, the persuasive form is: *"Here, X causes concrete cost C (evidence E). Alternative Y removes C at cost M. Given how often this code changes, I recommend Z (now / when next touched / accept and document)."* That framing survives disagreement about vocabulary, works with people who have never read the pattern catalogues, and produces a decision rather than a debate about names.
- Give an example where a widely condemned construct is the right choice.Service location at a plugin boundary: when the set of implementations is only known at runtime, some registry lookup is unavoidable. The discipline is to resolve once at the boundary and inject the result downward, rather than scattering lookups through domain code.
- A teammate labels code an anti-pattern in review. What do you ask for?The concrete consequence in this codebase, evidence for it (churn, defects, test pain, benchmarks), the proposed alternative with its migration cost, and a disposition — fix now, fix when next touched, contain, or accept and document.
Calling a tool 'wrong' out of context is like declaring a hammer defective because it fails at screws. The judgment has to name the job, the damage done, and the better tool available — otherwise it is taste, not engineering.
saying these in an interview costs you the question
- Using the label as the argument instead of naming the concrete consequence
- Ignoring the third part of the definition — a better alternative must exist
- Removing a construct without asking why it was introduced (Chesterton's fence)
- Judging code written for a script or a hot loop by large-system standards
- Treating pattern-heavy design as inherently better than plain concrete code