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.
// 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
| Criticism | Symptom in code | What to do instead |
|---|---|---|
| Missing language features | One-method interfaces, hand-written iterators | Use functions, generics, built-in protocols |
| Applied without understanding | Patterns in every class, no stated reason | State the force first |
| Over-engineering | Abstractions with one implementation | Wait for a real second variant |
| Hides the domain | Class names made of pattern words | Name after domain concepts |
| Brittle inheritance | Deep hierarchies, refused methods | Compose; inject behavior |
| Hard to debug | Global access, implicit wiring | Explicit 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
- What Is a Design PatternA design pattern is a named, reusable solution shape for a recurring design problem. Learn what one contains and how it differs from an algorithm or library.
- Why Learn PatternsWhat knowing design patterns buys a developer or coding agent: shared vocabulary, faster code reading, better refactoring targets, and knowing when to stop.
- Choosing a PatternA decision guide that maps concrete code symptoms to the pattern that fixes them, and tells you when plain code beats any pattern.
- Speculative GeneralityAbstractions, hooks and parameters built for future needs that never arrived make code harder to read without serving any current use.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.