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.

Scale Your Backend - Need an Experienced Backend Developer?

We provide backend engineers who join your team as contractors to help build, improve, and scale your backend systems.

We focus on clean backend design, clear documentation, and systems that remain reliable as products grow. Our goal is to strengthen your team and deliver backend systems that are easy to operate and maintain.

We work from our own development environments and support teams across US, EU, and APAC timezones. Our workflow emphasizes documentation and asynchronous collaboration to keep development efficient and focused.

  • Production Backend Experience. Experience building and maintaining backend systems, APIs, and databases used in production.
  • Scalable Architecture. Design backend systems that stay reliable as your product and traffic grow.
  • Contractor Friendly. Flexible engagement for short projects, long-term support, or extra help during releases.
  • Focus on Backend Reliability. Improve API performance, database stability, and overall backend reliability.
  • Documentation-Driven Development. Development guided by clear documentation so teams stay aligned and work efficiently.
  • Domain-Driven Design. Design backend systems around real business processes and product needs.

Tell us about your project

Our offices

  • Copenhagen
    1 Carlsberg Gate
    1260, København, Denmark
  • Magelang
    12 Jalan Bligo
    56485, Magelang, Indonesia

More articles

Imposter Syndrome Hits Hard: What to Do

That sinking feeling that you’re faking it, even when you’re not. Every developer, founder, or manager hits this wall at some point.

Read more

The Difference Between Continuous Integration and Continuous Delivery Most Teams Blur

CI and CD are not a single acronym — they are two distinct disciplines with different goals, failure modes, and organizational requirements. Conflating them explains why most pipelines deliver neither well.

Read more

Why Your Containers Can't Talk to Each Other

Container-to-container communication failures almost always trace back to a small set of root causes: wrong hostname, containers on different networks, or a service not actually listening on the expected address. Here's how to diagnose each one.

Read more

Zürich Backend Developer Rates Match Silicon Valley — Here Is What Startups Do Instead

You expected Swiss salaries to be high. You didn't expect your backend hire to cost more than the same role in San Francisco.

Read more