Loading content...
In the early 2000s, a phenomenon emerged in software development that experienced engineers still recall with a shudder: pattern-itis. After the Gang of Four book gained widespread popularity, eager developers began seeing patterns everywhere—and applying them everywhere. Codebases bloated with unnecessary abstractions. Simple problems drowned in architectural complexity. The cure became worse than the disease.
One legendary code review found a straightforward CRUD application with 17 design patterns applied. There was a Factory for the Factories, a Strategy for selecting Strategies, and an Observer observing the Observers. The original developer proudly explained each pattern's "purpose." The reviewer's assessment was blunt: "This is a 500-line script hidden inside 10,000 lines of pattern infrastructure."
This cautionary tale illustrates a crucial truth: knowing what patterns ARE is only half the education. You must also understand what patterns are NOT. Without this understanding, pattern knowledge becomes a liability rather than an asset.
By the end of this page, you will clearly understand what design patterns are not: not ready-made code, not goals in themselves, not requirements, not always appropriate, and not substitutes for thinking. This understanding protects you from the pattern abuse that undermines many projects.
Perhaps the most common misconception is treating patterns like recipes—find a pattern, copy its sample code, paste into your project, done. This fundamentally misunderstands the nature of patterns.
Patterns are templates for solutions, not solutions themselves.
Consider the difference:
Why copy-paste fails:
Sample code in pattern books is illustrative, not production-ready. It demonstrates the structure with minimal complexity. Real implementations must handle:
The right approach:
Understand the pattern's intent, structure, and participants. Then design your own implementation that fits your specific context. The pattern guides your thinking; it doesn't write your code.
Two valid implementations of the same pattern might share zero lines of code while sharing the same essential structure. That's the nature of patterns as templates, not code.
When implementing a pattern, use the pattern description to have a conversation with yourself: 'The pattern says I need a Subject and Observers. What's my Subject? What are my Observers? How do they communicate?' This dialog produces better implementations than copying sample code.
"We should use more patterns in this codebase." This statement reveals a dangerous mindset. Patterns are tools, not objectives. You don't earn points for patterns used.
The goal is good software, not patterned software.
Patterns are means to ends, and those ends are:
Patterns help achieve these goals in specific situations. But they're not the only way to achieve them, and they're not always the best way.
The checklist trap:
Some teams or individuals evaluate code by counting patterns. "Does it use Dependency Injection? Does it have a Factory? Is there proper separation of concerns with Strategy?" This checklist mentality leads to:
The right question isn't "What patterns should we use?"
It's "What problem do we have, and what's the best solution—which might or might not be a pattern?"
Starting with 'I want to use the Builder pattern' and then finding a place for it is backwards. Start with a problem: 'Object construction is complicated and error-prone.' Then recognize: 'This is the kind of problem Builder solves.' The problem leads to the pattern, not vice versa.
Every pattern has a context—conditions under which it applies. Outside that context, the pattern creates problems rather than solving them.
The cost of patterns:
Patterns aren't free. They introduce:
| Cost Type | Description | Example |
|---|---|---|
| Conceptual complexity | More concepts for developers to understand | New team member must learn your Observer implementation before contributing |
| Structural overhead | Additional classes, interfaces, and indirection | Strategy pattern adds an interface and concrete classes where a simple function might suffice |
| Performance overhead | Extra allocations, virtual calls, and indirection | Decorator chain adds memory and call overhead compared to direct implementation |
| Debugging difficulty | More places to look when things go wrong | Event passed through observer system is harder to trace than direct call |
| Maintenance burden | More code to update when requirements change | Changing an interface affects all implementations across multiple files |
When are patterns worth the cost?
Patterns pay for themselves when the problem they solve is actually present and the solution provides real benefit:
When are patterns overkill?
"You Aren't Gonna Need It" applies to patterns too. Don't add pattern infrastructure for flexibility you might need someday. Add it when you actually need it. Refactoring to a pattern later is often easier than living with unnecessary pattern complexity now.
The simplicity test:
Before applying a pattern, ask: "Can I solve this problem more simply?" If a problem can be solved with a simple function, a conditional, or direct code, do that. Patterns should be reached for when simpler approaches fall short—not as a first resort.
Pattern descriptions show archetypal structures—typically with classes named after their roles (Subject, Observer, ConcreteSubject). This leads some to believe there's a "correct" implementation that must be followed rigidly.
Patterns are adaptable, not rigid.
Consider the Observer pattern's canonical structure:
Subject
├── attach(Observer)
├── detach(Observer)
└── notify()
Observer (interface)
└── update()
ConcreteSubject extends Subject
ConcreteObserver implements Observer
Valid adaptations:
All of these are "Observer pattern"—they share the essential structure (one-to-many notification without tight coupling) while differing in implementation details. None is more "correct" than others; each fits different contexts.
Adapt to your ecosystem:
Languages and frameworks provide facilities that patterns can leverage:
The pattern's essence, not its sample code, is what matters. Implement it in a way that's idiomatic to your language, framework, and team conventions.
Ask: 'What problem does this pattern solve? What's the key structural insight?' Preserve that essence while adapting the form. A pattern implemented idiomatically in your language is better than a pattern that looks exactly like the book's Java example but fights against your ecosystem.
Perhaps the most pernicious misconception is treating patterns as a mechanical substitute for design thinking. "I have a problem; I'll find the matching pattern; done." This cargo-cult approach produces poor results.
Patterns require judgment:
None of these steps can be mechanized. They require engineering judgment—exactly the kind of thinking patterns are meant to support, not replace.
The pattern-matching trap:
Some developers maintain mental lookup tables:
This surface-level pattern-matching ignores crucial nuances:
new sufficient?Patterns guide, they don't decide.
A pattern is like a doctor's diagnosis: it provides a framework for action, but doesn't eliminate the need for professional judgment. Knowing the pattern is the beginning of the design process, not the end.
Patterns are tools for experienced engineers. They encode expert knowledge, but using them still requires experience. A novice with a pattern catalog may produce worse designs than without—misapplying patterns to inappropriate situations. Patterns amplify skill; they don't replace it.
Some developers assume everyone knows patterns. They use pattern names freely, assume understanding, and write code that's opaque to those unfamiliar with the patterns. This creates accessibility problems.
Reality check:
Inclusive practices:
PaymentStrategy both communicate the pattern and describe the domain conceptTeam calibration:
The level of pattern sophistication in your code should match your team's capabilities. A team of senior engineers fluent in patterns can use dense pattern-based designs productively. A mixed team might need more explicit documentation and simpler structures.
Patterns should aid collaboration, not hinder it. If your pattern-based code is only readable by you, the pattern is an anti-pattern.
Rather than dumbing down designs, consider raising team vocabulary over time. Pattern-of-the-week discussions, lunch-and-learn sessions, and paired design sessions help spread pattern knowledge. A team that learns together can design together at higher levels.
A pattern applied today may not be appropriate tomorrow. Requirements change, understanding deepens, and better solutions emerge. Treating patterns as permanent architecture leads to rigidity.
Patterns should be refactorable:
Just as you might refactor a function when its complexity grows, you should be able to:
Signs a pattern should be reconsidered:
| Signal | Possible Action |
|---|---|
| Pattern infrastructure is larger than business logic | Simplify; pattern may be overkill |
| Pattern flexibility is never exercised | Remove; YAGNI violation |
| Adding features requires fighting the pattern | Pattern no longer fits the problem |
| Team constantly asks 'why is this so complicated?' | Pattern may be inappropriate or poorly implemented |
| Performance testing shows pattern overhead is significant | Optimize or replace with direct implementation |
| Requirements have fundamentally changed since pattern was introduced | Re-evaluate; different pattern or no pattern may now fit better |
The evolution of design:
Good designs evolve. A system might start without patterns, acquire them as complexity grows, then simplify as the domain becomes better understood. This evolution is healthy.
Patterns are tools for a stage of development, not monuments to preserve forever. They should be as easy to refactor away as any other code—which means keeping them as simple as possible and avoiding over-engineering their implementation.
Martin Fowler's 'Refactoring to Patterns' captures this perfectly: patterns are often destinations of refactoring, not starting points. Write simple code. When complexity warrants, refactor to patterns. When patterns outlive their usefulness, refactor away from them.
Understanding what patterns are NOT is as important as understanding what they are. Let's consolidate the boundaries we've established:
The wise pattern practitioner:
Module Complete:
You now have a comprehensive understanding of what design patterns are and are not. You know they are reusable solutions to common problems, communication tools, and design vocabulary. You also know they are not code to copy, goals in themselves, or substitutes for engineering judgment.
This foundation prepares you for the next module, where we'll explore the history and origins of design patterns—the Gang of Four book that catalyzed the pattern movement and shaped modern software design thinking.
You've completed Module 1: What Are Design Patterns? You understand patterns as proven solutions, communication tools, and design vocabulary—while recognizing their limits and avoiding their misuse. Next, we'll dive into the history that brought us these powerful concepts.