# 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.

- kind: guide
- category: design-patterns
- aliases: Anti-patterns, Pattern overuse
- canonical: https://aipatterns.dev/guides/criticism-of-patterns
- inspired-by: https://refactoring.guru/design-patterns/criticism

## 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](https://aipatterns.dev/design-patterns/strategy.md) or [Command](https://aipatterns.dev/design-patterns/command.md) is a function value. With pattern matching over sealed types, much of [Visitor](https://aipatterns.dev/design-patterns/visitor.md) is a `match` or `switch` expression. With built-in iterators, you rarely write an [Iterator](https://aipatterns.dev/design-patterns/iterator.md) class.

```ts
// 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](https://aipatterns.dev/design-patterns/singleton.md) and uses it for every service. Another wraps every class in a [Factory](https://aipatterns.dev/design-patterns/factory-method.md) 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](https://aipatterns.dev/refactoring/smells/speculative-generality.md) 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](https://aipatterns.dev/guides/when-to-refactor.md).

## "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](https://aipatterns.dev/design-patterns/template-method.md) and some uses of [Factory Method](https://aipatterns.dev/design-patterns/factory-method.md) rely on subclassing. Deep hierarchies create the [Parallel Inheritance Hierarchies](https://aipatterns.dev/refactoring/smells/parallel-inheritance-hierarchies.md) and [Refused Bequest](https://aipatterns.dev/refactoring/smells/refused-bequest.md) smells.

**Rule:** prefer the composition-based version of a pattern. Pass a function or object in rather than subclass. See [Replace Inheritance with Delegation](https://aipatterns.dev/refactoring/techniques/replace-inheritance-with-delegation.md).

## "Patterns are hard to test or debug"

Indirection makes stack traces longer and behavior harder to trace. Global state in a [Singleton](https://aipatterns.dev/design-patterns/singleton.md) leaks between tests. Long [Observer](https://aipatterns.dev/design-patterns/observer.md) 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](https://aipatterns.dev/guides/choosing-a-pattern.md) has a "prefer no pattern when" section that turns this into a decision procedure.
