Case file PT3

Every Pattern Has a Failure Mode

Read the GoF Consequences section first.

Singleton is Global State. Visitor is “I gave up on polymorphism.” Observer is action at a distance. Each GoF pattern has a Consequences section that names the cost. Read it before reaching for the pattern, not after.

ByAdam LewisPublished3 May 2026Revised13 Oct 2026Reading12 minVersionv1.2ConfidenceHigh
§0b

Opinion

The most under-read pages in the Gang of Four book are the Consequences section in the middle of every pattern.1 Intent / Motivation / Applicability / Structure get all the airtime and the bodies are buried in the Consequences section, where Gamma et al. honestly list what each pattern costs you. Read seriously, the Consequences sections are a thirty-two-year-old catalogue of anti-patterns hiding in plain sight.

Singleton is a telling exception. Its GoF Consequences section lists five benefits and no liabilities: nothing on single-responsibility, hidden state or testing.2 Those costs were named later, outside the book. Misko Hevery wrote the canonical critique in 2008 (“Root Cause of Singletons”): a Singleton is Global State with a class name on it.3 Erich Gamma himself said in 2009 he would remove Singleton from a revised GoF.4 The pattern hasn't changed. The consensus has caught up with a cost its Consequences section never listed.

Visitor is the opposite case. The GoF Visitor chapter gives the diagnosis itself: Visitor uses double dispatch to add operations to classes without changing them, in languages like C++ and Smalltalk that support only single dispatch. “Languages that support double- or multiple dispatch lessen the need for the Visitor pattern.”1 Reaching for Visitor is admitting the type system cannot hold the constraint, and often the right move is to fix the types (sum types, pattern matching, a sealed hierarchy) rather than reach for the pattern. Observer is the action-at-a-distance trap: excellent for decoupling, a debugging nightmare. GoF's own Observer Consequences are honest about it: dependency criteria that aren't well-defined or maintained “usually lead to spurious updates, which can be hard to track down.”1 Refactoring Guru's Cons section adds one more: subscribers are notified in random order.6

Peter Norvig's 1996 essay extends the argument: sixteen of the twenty-three GoF patterns become invisible or simpler in a dynamic language with first-class functions.7 Factory Method becomes a class passed as a value. Strategy becomes a function. Command becomes a closure. The Consequences sections of those patterns include a hidden cost Norvig names out loud: in the wrong language, the pattern is ceremony with no payload. The discipline is not to reach for the pattern just because the catalogue offers it. Reach for it when the cost listed in the Consequences section is a cost worth paying.

Copy a note and link

Grab this short comment and drop it into a PR comment or an LLM chat to prompt the right change.

Before reaching for a pattern, name its failure mode. Singleton is Global State (mitigate with constructor injection). Visitor is polymorphism surrender (prefer sum types). Observer is action at a distance (mitigate with tracing). Read the GoF Consequences section first.

/tenet/every-pattern-has-a-failure-mode/PT3
Copy for paste
§0c

AI eyes only

Rule: when introducing a pattern, name its failure mode in the same paragraph.

Reject: introducing Singleton, Observer, Visitor, etc. without naming the cost. Reject: reaching for a pattern because it “looks like senior code”.

Generate: when introducing a pattern, write the cost and the mitigation alongside it. Example: “Singleton here. Cost is hidden state and harder testing. Mitigation is constructor injection at the boundary.”

Diagnostic: every pattern introduction must include a “cost: …; mitigation: …” line. No cost named, no pattern.

§0d

Why?

  • Turns the GoF Consequences section into a checklist. Each pattern has a documented cost list; PT3 makes reading it the cost of admission to using the pattern.
  • Names each pattern's failure mode out loud: Singleton is Global State, Visitor is Polymorphism Surrender, Observer is Action at a Distance, Factory is Yet Another Indirection. Reviewers can point at the failure mode by name.
  • For Visitor specifically, prefers the type-system answer over the pattern. Sealed sum types and exhaustiveness checking solve the original problem at compile time. Pairs with T1 Domain-Driven Types.
  • For Singleton, names the mitigation alongside the pattern: constructor injection at the boundary. Hidden state becomes explicit dependency. The testability cost evaporates.
  • Reads the Consequences section as a 2026 checklist rather than a 1994 verdict. Some costs aged out (Strategy's extra objects, when a strategy is a function). Some didn't (Observer's unexpected updates, in any language). The discipline is judging which apply.
  • The CLAUDE.md instruction “name the failure mode in the same paragraph as the pattern” turns agent pattern-fluency into honesty. The agent has read the Consequences section; PT3 asks it to remember.
  • Brown et al.'s AntiPatterns book is the systematic companion to GoF, forty entries pairing the success name with the failure name. PT3 makes the pairing the unit of decision.
The receipts
Origins, quoted passages, evidence, the strongest counter-argument and the reply.
§1

Origins

The Consequences section was in the GoF book from page one in 1994.1 Each of the 23 patterns has a Consequences section, and many name what you give up to get the pattern's benefits. Visitor: adding new ConcreteElement classes is hard. Observer: unexpected updates, and spurious updates “which can be hard to track down.” Singleton names none: all five of its listed consequences are benefits.2 The costs the authors did name were there from the start. The industry mostly ignored that section.

A popular critique landed fourteen years later. Misko Hevery's 2008 Google Testing Blog post “Root Cause of Singletons” named the failure mode in modern working-developer terms: a Singleton is Global State, and global state is transitive, so every object a Singleton reaches is global too.3 The post became the canonical reference for “Singleton is an anti-pattern, ” and arguably ended the pattern's career as a default OO design move.

Erich Gamma confirmed the consensus in a 2009 InformIT interview with Larry O'Brien: asked what he would change in a revised GoF, Gamma named Singleton as the pattern he would remove.4 He spoke for himself: the authors did not agree to drop it.11 The pattern hasn't changed. The consensus has caught up with a cost its Consequences section never listed.

The GoF Visitor chapter diagnosed the pattern itself: Visitor supplies double dispatch in single-dispatch languages like C++ and Smalltalk. “Languages that support double- or multiple dispatch lessen the need for the Visitor pattern.”1 Steve Yegge's “When Polymorphism Fails” (2004) never names Visitor, but it probes the same limit: polymorphism only makes sense when the behaviour belongs to the target, and when it belongs to the observer, Yegge reaches for runtime typing.5 Reaching for Visitor is admitting the type system can't hold the constraint. In a language with sealed sum types, the right move is to use the sum type. Yegge's broader “Execution in the Kingdom of Nouns” (2006) made the cultural case: pattern-as-class-hierarchy is one bar of the noun-cage.9

Brown, Malveau, McCormick and Mowbray's AntiPatterns book (1998) is the systematic catalogue of the failure-mode pairings.10 Forty entries, each with a name on the success side and a corresponding anti-pattern entry on the failure side. The book is dated in places, some entries have aged poorly, but the editorial move (every pattern has an anti-name) is exactly what PT3 names. Pair it with the GoF Consequences sections. The two together cover most of the pattern-as-failure-mode discipline.

Peter Norvig's 1996 essay closes the loop from a different direction.7 Sixteen of the twenty-three GoF patterns become invisible or simpler in a dynamic language with first-class functions. The implicit Consequences-section cost in those cases is “language ceremony you don't need.” Norvig wasn't anti-pattern. He was naming the cost the catalogue didn't name out loud.

§2

Quotes

I like to say that ”Singletons are global state in sheep’s clothing.” Most developers agree that global state is bad, but they love their Singletons.

Misko Hevery · Root Cause of Singletons (2008)

In the meantime, I hope I’ve made my main point clear, which is that polymorphism only makes sense when the polymorphic behavior is really a behavior of the target. When it’s the behavior of the observer, you need runtime typing.

Steve Yegge · When Polymorphism Fails (2004)

Study of the Design Patterns book: 16 of 23 patterns have qualitatively simpler implementation in Lisp or Dylan than in C++ for at least some uses of each pattern.

Peter Norvig · Design Patterns in Dynamic Languages (1996)
§3

Evidence

16 external sources, ranked by author authority. The first five are the canon; expand to see the rest, including the qualifiers and the named opposers. Each links out to its primary source.

  1. 01
    Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides · 1994
    Each of the 23 patterns has a numbered list of trade-offs: what you give up to get the benefits. Singleton: none (all five listed consequences are benefits). Visitor: hard to add new Element types. Observer: unexpected updates, spurious updates that are hard to track down. The catalogue authors were honest from page one; PT3 asks readers to remember.
  2. 02
    Misko Hevery · 2008
    The canonical modern critique. A Singleton is Global State with a class name on it. Global state is transitive, so every object a Singleton reaches is global too. Effectively ended Singleton's career as a default OO design move.
  3. 03
    Steve Yegge · 2004
    Polymorphism only makes sense when the behaviour belongs to the target; when it belongs to the observer, Yegge reaches for runtime typing. The essay does not name Visitor.
  4. 04
    Peter Norvig · 1996
    Sixteen of the 23 GoF patterns become invisible or simpler in a language with first-class functions and dynamic dispatch. The implicit cost the catalogue doesn't name: language ceremony you don't need. Norvig isn't anti-pattern. He is naming the cost out loud.
  5. 05
    Refactoring Guru · 2014 onwards
    Subscribers are notified in random order: the one con Refactoring Guru lists. The updates that are hard to track down come from GoF's own “unexpected updates” consequence.
  1. 06
    William J. Brown, Raphael C. Malveau, Hays W. McCormick, Thomas J. Mowbray · 1998
    The systematic catalogue of failure-mode pairings. Forty entries, each with a success name and an anti-pattern entry on the failure side. Dated in places, but the editorial move (every pattern has an anti-name) is exactly what PT3 names.
  2. 07
    Larry O'Brien interviewing Erich Gamma · 2009
    Asked what he would change in a revised GoF, Gamma names Singleton as the pattern he would remove. The Consequences section was right. The consensus has caught up.
  3. 08
    Wikipedia contributors · 2024
    Mainstream summary. Lists hidden state, testability, single-responsibility violation, multi-threading complexity. The Criticism section lists the costs that GoF's original Consequences entry never named.
  4. 09
    Sara H. S. Almadi et al. · 2021
    Systematic review of 16 studies from ten years of literature on the bad smells (code smells and grime) found in GoF pattern implementations. Maps specific patterns to specific code smells. The empirical companion to the catalogue's Consequences sections.
  5. 10
    Sandi Metz · 2018
    The deepest single treatment of when not to use a structural pattern. Inheritance, Template Method's vehicle, is a powerful tool with a documented failure mode (deep hierarchies, fragile base class). Metz names the cost in working-developer terms.
  6. 11
    Becheler · 2025
    Modern pragmatic restatement of Hevery's case for a working C++ audience. Useful Layer 2 corroboration that the Singleton failure-mode argument generalises across language ecosystems.
  7. 12
    GeeksforGeeks contributors · 2024
    Common-knowledge restatement that confirms the position is mainstream and uncontroversial. Useful as evidence of consensus, not as primary source. The argument is Hevery's.
  8. 13
    Joshua Bloch · 2017
    Names the inheritance failure mode at industrial Java scale: subclassing across package boundaries breaks encapsulation. The catalogue-author honestly listing the cost of one of the catalogue's own structural moves.
  9. 14
    Steve Yegge · 2006
    The cultural-not-technical critique. Pattern-as-class-hierarchy is one bar of the noun-cage. The implicit cost the catalogue doesn't name is the language friction it creates in non-OO settings.
  10. 15
    Refactoring Guru · 2014 onwards
    Modern accessible Cons section: violates SRP, masks bad design, hard to unit-test. The Refactoring Guru pages are honest about each pattern's failure mode; PT3 says read the Cons section first.
  11. 16
    Refactoring Guru · 2014 onwards
    Names the Visitor failure mode plainly: every change to the element hierarchy forces every concrete Visitor to be updated. The cost the original GoF Consequences section flagged, restated for a 2026 audience.

Sixteen sources clustered tightly around the four named failure modes. The canonical critiques sit at the top: Gamma et al. on the Consequences sections, Hevery on Singleton, Yegge on where polymorphism fails, Norvig on dynamic-language patterns, plus Refactoring Guru's own Cons sections. Norvig, Bloch and Yegge's “Execution in the Kingdom of Nouns” qualify rather than support. None opposes. The reply argues the rule is not against patterns, it is against unconscious adoption.

§4

Examples

Viewing: TypeScript.
Avoid

Before Singleton hides global state; no seam for a test fake.

Filehedgehog-tracker.ts
export class HedgehogTracker {
  static instance = new HedgehogTracker();
  private sightings: Sighting[] = [];
  record(sighting: Sighting): void { this.sightings.push(sighting); }
}

HedgehogTracker.instance.record(sighting);
Prefer

After tracker is injected; the test passes a fake.

Filepatrol.ts
export class HedgehogTracker {
  private sightings: Sighting[] = [];
  record(sighting: Sighting): void { this.sightings.push(sighting); }
}

export class Patrol {
  constructor(private readonly tracker: HedgehogTracker) {}
  log(sighting: Sighting): void { this.tracker.record(sighting); }
}
§4c

AI rules

File.cursor/rules/pt3-every-pattern-has-a-failure-mode.mdc
---
alwaysApply: true
---

## Prickles PT3: Every Pattern Has a Failure Mode

Before introducing a pattern, read its Consequences (or Cons) section in GoF / Refactoring Guru / the canonical reference. Name the cost out loud in the same paragraph as the choice.

For Singleton, name the mitigation: constructor injection at the boundary, never module-level `getInstance()`. Hidden state becomes explicit dependency.

For Visitor, prefer the type-system answer when the language supports it: sealed sum types, discriminated unions, exhaustiveness checking. Visitor is the fallback for languages that can't double-dispatch.

For Observer, document the cause-and-effect chain. Action at a distance is the documented failure mode. Mitigate with a tracing layer or named events that survive grep.

Refuse to introduce a pattern without naming its failure mode. The Consequences section was honest in 1994. The agent has read it. The rule is to remember.

Repo layout, CI, and ESLint wiring for these paths live on /implementation, not repeated on every tenet.

§5

Counter-argument

Counter

The honest steelman is that some patterns' Consequences sections name costs that are overblown for modern codebases. The 1994 Strategy Consequences section lists an increased number of objects among its drawbacks, mostly irrelevant when a strategy is a plain function. Treating the Consequences section as gospel risks reading 1994 trade-offs as 2026 ones, and PT3's “every pattern has a failure mode” line risks calcifying that misreading. The honest counter: read the Consequences section and ask whether the cost still applies in the current language and runtime.

The second steelman is Kerievsky's Refactoring to Patterns8 + Fowler's rule of three: do not reach for a pattern until the third caller arrives, because earlier pattern-naming risks the wrong abstraction. The argument: read the Consequences section, then wait for three identical-shaped uses before naming the pattern.

§6

Counter-argument retort

Reply

The counter is right that some 1994 costs are 2026 non-issues. The reply is to read the Consequences section as a checklist rather than a verdict. Strategy's 1994 worry about an increased number of objects is irrelevant when a strategy is a plain function. Observer's 1994 worry about unexpected updates is exactly as relevant now. The discipline isn't “reject the pattern because the catalogue named a cost”; it's “name which Consequences items still apply, and decide consciously.”

The Hevery objection3 survives every modernisation of the Consequences section. Hidden state and hard testing are language-independent costs of Singleton. The cost shows up in TypeScript exactly as it shows up in Java. Constructor injection at the boundary (pass the dependency in, store it on the object, never reach for a module-level getInstance()) is the mitigation and it is also the answer in every language. PT3 doesn't forbid Singleton. It asks you to name the cost and the mitigation in the same paragraph as the choice.

The GoF double-dispatch note1 cuts the same way for Visitor. In a language with sealed sum types and pattern matching (Rust, modern TypeScript with discriminated unions, F#, Scala, Kotlin) an exhaustive match does Visitor's job without the double-dispatch plumbing. Visitor's 1994 cost (must touch every ConcreteVisitor when adding a new Element class) does not go away: a new variant needs a new arm in every match, and exhaustiveness checking makes the compiler list each one. So PT3 in a 2026 sum-type-rich language reads: prefer the type-system answer. Reach for Visitor only when the language genuinely doesn't support double-dispatch. The discipline updates with the tooling. The “read the Consequences section” rule doesn't.

The agent dimension is the closing argument. Agents trained on legacy corpora reach for Singleton because the corpora are full of Singletons. The failure mode is the agent releasing the 1994 cost into a 2026 codebase without naming it. The CLAUDE.md instruction that PT3 lives behind, name the failure mode in the same paragraph as the pattern, turns the agent's pattern-fluency into a feature instead of a bug. The agent has read the Consequences section. The rule asks it to remember.

The rule-of-three steelman (Kerievsky / Fowler / Beck: wait for three callers before naming a pattern) is acknowledged and refused. Don't wait for three. The Roberts/Fowler rule of three is an artefact of the pre-AI era when the cost of detecting and renaming an emerging pattern was high. With agents reading the diff before the human does, the cost of an early pattern name is lower than the cost of three near-identical Strategies drifting independently for two sprints. Name the pattern when the second caller appears and the Consequences section's costs apply. If a third caller reshapes the abstraction, the agent renames it. The wrong pattern name is cheap to fix. The duplicated near-Strategy that slipped past is not.

§7

Notes

  1. [1]Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994). Each pattern's Consequences section names the trade-offs honestly. Most readers stop at Intent / Motivation. The bodies are buried at the back.
  2. [2]Erich Gamma et al.: Singleton Consequences (op. cit.). Lists five benefits and no liabilities: controlled access to the sole instance, reduced name space, refinement of operations and representation, a variable number of instances and more flexibility than class operations.
  3. [3]Misko Hevery: “Root Cause of Singletons” (Google Testing Blog, 2008). The canonical modern critique: a Singleton is Global State, and global state is transitive, so everything a Singleton reaches is global too.
Disagree? Found a hole in the argument? Take issue with this tenet →
Last revised: 2026-10-13