A design pattern is a named, reusable solution to a recurring design problem in a specific context, described abstractly enough to be applied in many programs but concretely enough to guide the structure of classes, objects, functions and their interactions. In programming and software architecture, a pattern is not finished code or a library: it is a documented arrangement of responsibilities that experienced developers recognize, discuss by name and adapt to the language and system at hand.
The idea was borrowed from the building architect Christopher Alexander and entered software through the 1994 book by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides, the so-called Gang of Four (GoF), which catalogs 23 object-oriented patterns. Their value lies in three things: a shared vocabulary that compresses design discussions, proven trade-offs that save teams from rediscovering pitfalls, and a structure that makes code easier to change in the directions the pattern anticipates.
How a design pattern is described
A design pattern is described by a fixed set of elements that together explain when the solution applies and what it costs. The name is only the handle; the context, forces and consequences carry the real knowledge, because the same structure can be the right choice in one system and needless complexity in another.
The following table lists the elements of a classic pattern description and what each one tells the developer.
| Element | What it states | Why it matters |
|---|---|---|
| Name | A short, memorable label such as Observer | Creates a shared design vocabulary |
| Intent | The problem the pattern solves in one sentence | Lets a reader judge relevance quickly |
| Context and forces | The situation and competing pressures | Defines when the pattern applies |
| Structure | Participants, roles and relationships | Shows how responsibilities are split |
| Consequences | Benefits, costs and side effects | Exposes the trade-off being made |
| Known uses | Systems where the pattern appears | Shows that the solution is proven |
Most patterns rest on a few recurring principles: program to an interface, not an implementation, favor object composition over class inheritance, and encapsulate what varies. A pattern typically isolates one axis of change behind an abstraction so that new behavior can be added without editing existing, tested code, which is the open-closed principle in practice.
The three classic categories of design patterns
The Gang of Four catalog groups patterns by purpose into creational, structural and behavioral categories. Creational patterns control how objects are instantiated, structural patterns control how objects and classes are composed, and behavioral patterns control how objects communicate and distribute responsibility.
The table below summarizes each category with representative patterns and the design question each one answers.
| Category | Core question | Representative patterns | Count in GoF |
|---|---|---|---|
| Creational | Who creates the object, and how | Factory Method, Abstract Factory, Builder, Prototype, Singleton | 5 |
| Structural | How objects are combined into larger structures | Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy | 7 |
| Behavioral | How objects interact and share work | Strategy, Observer, Command, State, Iterator, Template Method and others | 11 |
The counts add up to the 23 patterns of the original catalog. Later catalogs extended the idea well beyond object-oriented code: enterprise application patterns such as Repository and Unit of Work, concurrency patterns such as Producer-Consumer and Thread Pool, and distributed system patterns such as Circuit Breaker, Saga and Event Sourcing.
Design patterns compared with related concepts
A design pattern operates at the level of a few collaborating components, which distinguishes it from architectural styles above it and from idioms below it. Confusing these levels leads to misplaced expectations, for example treating Microservices as if it were a class-level pattern or expecting an idiom to shape a whole system.
The table compares the design pattern with neighboring concepts in software architecture.
| Concept | Scope | Example | Relation to design patterns |
|---|---|---|---|
| Idiom | One language construct | Resource acquisition is initialization in C++ | Lower level and language-specific |
| Design pattern | A few classes or modules | Strategy, Observer, Adapter | The reference level |
| Architectural pattern | A whole application or system | Layered architecture, Clean Architecture, Microservices | Higher level; often built from many design patterns |
| Framework | Reusable executable code | A web or dependency injection framework | Implements patterns rather than describing them |
| Anti-pattern | Any level | God Object, Golden Hammer | A common solution that causes more harm than good |
Dependency Injection shows how the levels interact: it is a technique for supplying an object with its collaborators from outside, and it makes patterns such as Strategy practical because the concrete strategy can be chosen by configuration rather than hard-coded.
Choosing and applying a pattern
A pattern should be applied in response to a concrete, recurring problem, not because the codebase seems to need more structure. Experienced developers usually start with the simplest working code and refactor toward a pattern when the second or third variation of the same change appears.
The following decision table maps frequent design problems to a suitable pattern and the reason it fits.
| Situation | Suitable pattern | Reason |
|---|---|---|
| An algorithm must be swappable at runtime | Strategy | Encapsulates each variant behind one interface |
| Several components must react to a state change | Observer | Decouples the source from its listeners |
| A third-party API does not match the expected interface | Adapter | Translates one interface into another |
| Behavior must be added without subclass explosion | Decorator | Wraps objects and stacks features by composition |
| Object construction has many optional parameters | Builder | Separates step-by-step construction from representation |
| A complex subsystem needs a simple entry point | Facade | Hides internal detail behind one coherent interface |
Language features change the cost of a pattern. In languages with first-class functions, Strategy and Command often reduce to passing a function, and Iterator is built into the language. A pattern is therefore best understood as a solution to a problem, not as a fixed class diagram that must be reproduced line by line.
Limitations and common mistakes
The main risk of design patterns is overengineering: adding indirection, interfaces and classes for flexibility that the system never needs. Every pattern has a cost in extra abstraction and reading effort, and that cost is justified only when the anticipated variation actually occurs.
- Pattern-first design: choosing patterns before understanding the problem produces code shaped like a catalog rather than like the domain.
- Singleton overuse: a Singleton is effectively global mutable state, which hides dependencies, complicates testing and causes trouble under concurrency.
- Literal copying: reproducing the textbook class diagram in a language that offers a simpler native construct adds needless code.
- Wrong naming: calling a class a Factory or Manager without following the pattern misleads readers and destroys the shared vocabulary.
- Ignoring consequences: Observer, for example, can cause memory leaks when listeners are never unsubscribed and hard-to-trace update cascades.
A useful rule is that a pattern should remove more complexity than it adds. If a reviewer cannot name the variation that the pattern protects against, the simpler design is usually the better one.
A non-technical view
A design pattern is like a proven recipe for a common building problem. Carpenters do not reinvent how to join two boards at a corner every time; they use a known joint that has a name and a reputation. Software developers do the same with recurring problems, such as letting several parts of a program react when something changes.
The pattern is not the finished product. It is the idea of how to arrange the parts, which each developer then builds in their own materials. Because the idea has a name, a team can say “use an Adapter here” and everyone understands the plan in seconds. The key takeaway is that patterns save time and mistakes, but only when the problem really matches the recipe.
A concrete example
An online store calculates shipping costs. At first, the code contains one function with a long chain of conditions for standard, express and international delivery. When the business adds pickup points and a same-day courier, every change means editing the same fragile function, and tests for unrelated options start failing.
The team refactors the logic into the Strategy pattern. A ShippingStrategy interface defines one method that returns a price for an order, and each delivery option becomes a separate class with its own tests. The checkout selects the strategy from the customer’s choice, and the concrete classes are supplied through dependency injection.
The outcome is measurable in daily work: adding a sixth delivery option now means writing one new class without touching existing ones, and a pricing bug in international shipping can no longer break standard delivery. The team rejected a more elaborate plugin system because only a handful of options were expected, which kept the design as simple as the problem allowed.