Design Patterns 101: Why the Gang of Four Changed Software Forever¶
Let me paint you a picture. It’s 1993. You’re a software developer. You’ve just spent three weeks building an elegant notification system. It’s beautiful. It works. You’re proud of it.
Then your colleague walks over, looks at your screen, and says: “Oh, you built an Observer.”
Wait, what?
Turns out, the problem you solved? Thousands of developers before you solved the exact same one. And they all arrived at roughly the same solution. You didn’t invent anything, you just didn’t know there was already a name for it.
That is the core idea behind design patterns: naming solutions to problems that keep showing up, over and over, in software development. And in 1994, four very smart people decided to write them all down.
The Gang of Four: A Brief Origin Story¶
In 1994, four authors (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) published a book that would change software engineering forever: “Design Patterns: Elements of Reusable Object-Oriented Software.”
These four became known as the Gang of Four (GoF), which sounds like either a programming legends group or a heist movie crew. Either way, the book delivered.
They cataloged 23 design patterns, organized into three categories. The book became one of the most influential texts in software engineering history. Over 30 years later, these patterns are still taught in every serious CS program, asked about in interviews, and used daily by developers worldwide.
“The Gang of Four didn’t invent these patterns. They discovered them, like biologists cataloging species that already existed in the wild.”
So What Exactly IS a Design Pattern?¶
A design pattern is a general, reusable solution to a commonly occurring problem in software design. Think of it as a blueprint, not finished code. You cannot copy-paste a design pattern into your project, it’s a template that shows you how to structure your solution.
Here’s an analogy. Think about architecture (the building kind, not the software kind). An architect does not reinvent the concept of a “doorway” for every building. Instead, they draw on established patterns:
- Double doors for high traffic areas
- Revolving doors for climate control
- Sliding doors for tight spaces
Each is a known solution to a known problem. Software design patterns work the same way. You have a problem? There is probably a pattern for that.
The Three Categories¶
The GoF organized their 23 patterns into three categories based on what kind of problem they solve:
| Category | What It Deals With | How Many |
|---|---|---|
| Creational | How objects get created, making sure you create objects in a way that’s suitable to the situation | 5 |
| Structural | How objects are composed, building relationships between objects to form larger, flexible structures | 7 |
| Behavioral | How objects communicate, defining how objects interact and distribute responsibility among themselves | 11 |
Let’s break these down so they actually make sense.
Creational Patterns: “How Do I Build This Thing?”¶
You might think creating an object is simple, just slap a new keyword and you’re done. But in real applications, object creation can get surprisingly complex:
- What if you need to ensure only ONE instance exists? (Singleton)
- What if the object has 15 optional parameters? (Builder)
- What if you don’t know which exact type to create until runtime? (Factory Method)
- What if you need to create families of related objects that work together? (Abstract Factory)
- What if creating a new object is expensive and you’d rather clone an existing one? (Prototype)
Creational patterns give you battle-tested ways to handle all of these situations.
Structural Patterns: “How Do I Organize These Pieces?”¶
Once you have your objects, you need to compose them into larger structures. Structural patterns help you build these relationships cleanly:
- Your old API doesn’t match what your new code expects? Wrap it. (Adapter)
- You want to add new behavior to an object without changing it? Layer it. (Decorator)
- Your subsystem has 47 classes and the client just wants ONE simple method? Simplify it. (Facade)
- You have thousands of similar objects eating all your RAM? Share them. (Flyweight)
These patterns are all about making objects play nicely together without creating tangled messes.
Behavioral Patterns: “How Should These Things Talk to Each Other?”¶
This is the largest category (11 patterns!) because communication between objects is where most of the complexity lives:
- One object changes and a bunch of others need to know? (Observer)
- You need to swap algorithms at runtime without if/else chains? (Strategy)
- You want undo/redo functionality? (Command + Memento)
- You need to process a request through a chain of handlers? (Chain of Responsibility)
Behavioral patterns keep your objects communicating without becoming a tangled web of dependencies.
All 23 Patterns at a Glance¶
Here is the complete roster. Think of this as your field guide, you don’t need to memorize every one right now, but it is incredibly useful to know they exist so you can look them up when you hit the right problem.
Creational Patterns (5)¶
| Pattern | What It Does (One-Liner) |
|---|---|
| Abstract Factory | Creates families of related objects without specifying their concrete classes |
| Builder | Constructs complex objects step by step, separating construction from representation |
| Factory Method | Defines an interface for creating objects, letting subclasses decide which class to instantiate |
| Prototype | Creates new objects by cloning an existing object (the prototype) |
| Singleton | Ensures a class has only one instance and provides a global access point to it |
Structural Patterns (7)¶
| Pattern | What It Does (One-Liner) |
|---|---|
| Adapter | Converts the interface of a class into another interface clients expect |
| Bridge | Separates an abstraction from its implementation so the two can vary independently |
| Composite | Composes objects into tree structures to represent part-whole hierarchies |
| Decorator | Attaches additional responsibilities to an object dynamically |
| Facade | Provides a simplified interface to a complex subsystem |
| Flyweight | Shares fine-grained objects efficiently to support large numbers of similar objects |
| Proxy | Provides a surrogate or placeholder for another object to control access to it |
Behavioral Patterns (11)¶
| Pattern | What It Does (One-Liner) |
|---|---|
| Chain of Responsibility | Passes a request along a chain of handlers until one handles it |
| Command | Encapsulates a request as an object, allowing parameterization, queuing, and undo |
| Interpreter | Defines a grammar and an interpreter for a language |
| Iterator | Provides a way to access elements of a collection sequentially without exposing its underlying representation |
| Mediator | Defines an object that encapsulates how a set of objects interact |
| Memento | Captures and restores an object’s internal state without violating encapsulation |
| Observer | Defines a one-to-many dependency so that when one object changes state, all its dependents are notified |
| State | Allows an object to alter its behavior when its internal state changes |
| Strategy | Defines a family of algorithms, encapsulates each one, and makes them interchangeable |
| Template Method | Defines the skeleton of an algorithm, letting subclasses override specific steps |
| Visitor | Lets you define new operations on elements of a structure without changing the elements themselves |
Complete Quick Reference¶
For the visual thinkers, here is every single pattern in one numbered table:
| # | Pattern | Category | Key Purpose |
|---|---|---|---|
| 1 | Abstract Factory | Creational | Families of related objects |
| 2 | Builder | Creational | Step-by-step complex object construction |
| 3 | Factory Method | Creational | Delegate instantiation to subclasses |
| 4 | Prototype | Creational | Clone existing objects |
| 5 | Singleton | Creational | Single instance with global access |
| 6 | Adapter | Structural | Interface compatibility |
| 7 | Bridge | Structural | Separate abstraction from implementation |
| 8 | Composite | Structural | Tree structures, part-whole hierarchies |
| 9 | Decorator | Structural | Dynamic responsibility attachment |
| 10 | Facade | Structural | Simplified subsystem interface |
| 11 | Flyweight | Structural | Shared fine-grained objects |
| 12 | Proxy | Structural | Controlled access surrogate |
| 13 | Chain of Responsibility | Behavioral | Pass request along handler chain |
| 14 | Command | Behavioral | Encapsulate request as object |
| 15 | Interpreter | Behavioral | Language grammar and interpretation |
| 16 | Iterator | Behavioral | Sequential collection access |
| 17 | Mediator | Behavioral | Centralized object interaction |
| 18 | Memento | Behavioral | Capture and restore state |
| 19 | Observer | Behavioral | One-to-many state change notification |
| 20 | State | Behavioral | Behavior changes with internal state |
| 21 | Strategy | Behavioral | Interchangeable algorithms |
| 22 | Template Method | Behavioral | Algorithm skeleton with overridable steps |
| 23 | Visitor | Behavioral | New operations without changing elements |
When to Use Patterns vs. When NOT To¶
This is the part most tutorials skip, but it might be the most important section in this entire article. Design patterns are powerful, but they are also dangerously easy to overuse. There is even a name for it: Pattern Fever.
Use Patterns When:¶
- You recognize a recurring problem that a pattern directly addresses
- The codebase is growing and needs structure to stay maintainable
- You need flexibility for future requirements (swappable algorithms, pluggable components)
- Multiple team members need a shared vocabulary to discuss design decisions
- You are building a library or framework that others will extend
Do NOT Use Patterns When:¶
- A simple solution works fine, seriously, a 10-line function does not need a Strategy pattern
- You are speculating about future requirements (YAGNI, You Aren’t Gonna Need It)
- The pattern adds more complexity than it solves (this happens more often than you’d think)
- You are working on a small, throwaway script
- You are using a pattern just because you learned about it yesterday and you’re excited (we have all done this, no judgment)
The Golden Rule: If applying a pattern makes the code harder to read and understand, you are using it wrong. Patterns should simplify communication about design, not obscure it behind layers of abstraction nobody asked for.
Here is a good litmus test: if you have to explain your pattern choice with a 20-minute whiteboard session and your colleague’s eyes glaze over, you’ve probably over-engineered it.
Why don’t design patterns ever get lonely? Because they always come in a Gang of Four!
What’s Next?¶
Now that you know what design patterns are, why they exist, and when (and when NOT) to use them, it is time to get your hands dirty with actual implementations.
The best place to start? The Singleton pattern. It is the simplest GoF pattern to understand, the most commonly used (and misused!), and it will teach you a surprising amount about thread safety, lazy initialization, and why some developers have very strong opinions about it.
Head over to our next article: “The Singleton Pattern, 6 Ways to Do It in C# (And Which One Wins)” where we compare every Singleton implementation from the naive approach that breaks in production to the modern Lazy<T> approach that makes everything elegant.
From there, we will continue diving into individual patterns with real code, real trade-offs, and real-world applications. The journey from knowing about patterns to instinctively reaching for the right one at the right time, that is where the real growth happens.
“The best developers know when NOT to use a pattern. Start simple. When you feel the pain of a design problem (duplication, tight coupling, rigidity) reach for the pattern that addresses that specific pain.”
See you in the next one!