Skip to content
aipatterns.dev

Guide

Criticism of Patterns

The main arguments against design patterns, including missing language features, misuse, and over-engineering, and how to apply patterns without those failures.

Also known as
Anti-patterns, Pattern overuse

Why the criticism matters

Patterns are a tool with a well-known failure mode: applying them where they don't fit. Most criticism of patterns is criticism of misuse, and each argument maps to a rule you can follow. This page lists the arguments and the rule each one implies.

"Patterns are workarounds for weak languages"

Several classic patterns exist to supply features that early-1990s C++ lacked. In a language with first-class functions, a one-method Strategy or Command is a function value. With pattern matching over sealed types, much of Visitor is a match or switch expression. With built-in iterators, you rarely write an Iterator class.

TypeScript
// Ceremony: a Command interface and a class per action.
interface Command { execute(): void }
class SaveCommand implements Command {
  constructor(private doc: Doc) {}
  execute() { this.doc.save(); }
}

// Same intent in TypeScript when no undo or metadata is needed.
type Action = () => void;
const save: Action = () => doc.save();

Rule: before writing an interface plus classes, ask whether a function, a closure, a union type or a standard library feature already plays the role.

"Patterns are applied without understanding"

A developer learns Singleton and uses it for every service. Another wraps every class in a Factory because "factories are good practice". The result is indirection without a reason.

Rule: name the force the pattern resolves. If you can't state it in one sentence tied to this codebase ("we need to add payment providers without editing checkout"), don't apply the pattern.

"Patterns cause over-engineering"

Patterns introduce types, interfaces and indirection. Applied for a future that never comes, they are the Speculative Generality smell. A reader now jumps through four files to find code that once fit on one screen.

Rule: apply a pattern to solve a problem you have now, or one that is concretely planned. Refactor toward it later when the second or third variant actually arrives. Refactoring is cheap when tests exist. See When to refactor.

"Patterns hide the domain"

Code full of AbstractProcessorFactoryProvider names tells the reader about the plumbing and nothing about the business. Pattern names belong in documentation and commit messages more than in class names.

Rule: name classes after domain concepts (ShippingRule, InvoiceExporter). Mention the pattern in a short comment or doc only when it helps the reader.

"Inheritance-based patterns are brittle"

Template Method and some uses of Factory Method rely on subclassing. Deep hierarchies create the Parallel Inheritance Hierarchies and Refused Bequest smells.

Rule: prefer the composition-based version of a pattern. Pass a function or object in rather than subclass. See Replace Inheritance with Delegation.

"Patterns are hard to test or debug"

Indirection makes stack traces longer and behavior harder to trace. Global state in a Singleton leaks between tests. Long Observer chains make "who changed this?" hard to answer.

Rule: keep wiring explicit at one composition root, avoid global access points, and prefer constructor injection so tests can substitute parts.

Summary table

CriticismSymptom in codeWhat to do instead
Missing language featuresOne-method interfaces, hand-written iteratorsUse functions, generics, built-in protocols
Applied without understandingPatterns in every class, no stated reasonState the force first
Over-engineeringAbstractions with one implementationWait for a real second variant
Hides the domainClass names made of pattern wordsName after domain concepts
Brittle inheritanceDeep hierarchies, refused methodsCompose; inject behavior
Hard to debugGlobal access, implicit wiringExplicit composition root

What survives the criticism

None of these arguments says the problems that patterns address are imaginary. Systems still need to swap algorithms, adapt third-party APIs, notify listeners and build complex objects. The useful position is narrow: know the pattern, know its cost, and apply the smallest structure that fits. Choosing a pattern has a "prefer no pattern when" section that turns this into a decision procedure.

See also

Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.