Why Design Patterns Are Useful Until They Become an Obsession
by Eric Hanson, Backend Developer at Clean Systems Consulting
The Vocabulary Problem
When the Gang of Four published Design Patterns in 1994, they gave the industry a shared vocabulary for recurring structural problems in object-oriented code. That vocabulary is genuinely valuable — saying "this should be a Strategy pattern" in a code review communicates more precisely than a paragraph of description.
The problem came later, when the vocabulary became aspirational. When developers stopped using pattern names to describe existing structure and started using them as design goals. When "we should use the Observer pattern here" stopped being a recognition and started being a prescription.
A pattern is a solution to a problem you already have. When you apply it to a problem you might have someday, it stops being a solution and starts being complexity.
What Patterns Actually Are
Each pattern in the original catalog documents three things: the problem context, the solution structure, and the consequences (including the costs). The consequences are the part most people skip.
The Factory Method pattern, for example, makes it easy to add new product types without changing the creator. The cost: you've added a class hierarchy. If you only have one product type, you've added overhead — classes, files, cognitive load — for flexibility you don't use.
The Observer pattern decouples event producers from consumers. The cost: indirect control flow. Debugging an event-driven system means tracing through dispatch tables instead of following call stacks. When something doesn't fire, finding why is harder than in a direct call.
The Decorator pattern lets you add behavior to objects dynamically. The cost: deep wrapping chains are hard to inspect and debug. A well-known example: Java's InputStream hierarchy, where fully buffering a file with character decoding requires new BufferedReader(new InputStreamReader(new FileInputStream(path))). Correct, composable, and confusing to everyone who hasn't memorized the chain.
The Obsession Failure Mode
The failure mode is recognizable. A developer who has recently studied patterns begins to see every problem as a pattern-fitting exercise. They don't ask "what is the simplest structure that solves this?" They ask "which pattern does this look like?"
The result is pattern theater — code that is demonstrably pattern-compliant and harder to work with than the naive implementation would have been.
A real example: a small service that processes three types of payment events. The naive implementation is a method with three cases:
void process(PaymentEvent event) {
switch (event.getType()) {
case CHARGE: handleCharge(event); break;
case REFUND: handleRefund(event); break;
case DISPUTE: handleDispute(event); break;
}
}
This is fourteen lines. It is completely legible. Every developer on the team can read it and understand what happens for any event type.
The pattern-obsessed version introduces a PaymentEventHandler interface, three handler implementations, a HandlerRegistry that maps event types to handlers, a factory to build the registry, and possibly a Chain of Responsibility in case handlers need to be composed. This is now six files, sixty lines of non-test code, and requires a full mental model of the dispatch mechanism before you can answer "what happens when a refund event comes in?"
The original three-case switch becomes a liability if you have thirty event types or if handlers need to be dynamically registered at runtime. With three fixed types, it is strictly better.
When Patterns Are Genuinely Useful
Patterns earn their complexity when the problem they solve is present and real:
- Strategy earns its place when you have multiple algorithms for the same operation that genuinely need to be swapped — different pricing models per region, different rendering strategies per client type, different validation rules per product category. Not "we might want to swap this later."
- Repository earns its place when you need to abstract data access for testing or when you genuinely have multiple data sources. Not as a reflexive wrapper around every database query.
- Event/Observer earns its place when you have genuinely decoupled subsystems where the producer should not know what consumes its output. Not as a way to avoid direct method calls within the same bounded context.
The test: can you name the specific problem you're solving with this pattern today, not hypothetically? If yes, proceed. If not, wait.
The Right Mental Model
Think of patterns as a toolkit you reach into when the job calls for a specific tool. A skilled carpenter doesn't put a mortise in every joint — they use it when it's the right joint for the load and material. The value is in knowing the toolkit exists and what each tool is for. Not in demonstrating fluency by using as many tools as possible.
The Practical Takeaway
The next time you're tempted to introduce a pattern, write down: (1) the specific problem you have right now that this pattern addresses, (2) the cost the pattern introduces, and (3) whether the problem is severe enough to pay that cost today. If you can't complete step one specifically, wait. The pattern will still be there when the problem arrives.