Design pattern

On this page 7

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.

ElementWhat it statesWhy it matters
NameA short, memorable label such as ObserverCreates a shared design vocabulary
IntentThe problem the pattern solves in one sentenceLets a reader judge relevance quickly
Context and forcesThe situation and competing pressuresDefines when the pattern applies
StructureParticipants, roles and relationshipsShows how responsibilities are split
ConsequencesBenefits, costs and side effectsExposes the trade-off being made
Known usesSystems where the pattern appearsShows that the solution is proven
Elements of a pattern description

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.

CategoryCore questionRepresentative patternsCount in GoF
CreationalWho creates the object, and howFactory Method, Abstract Factory, Builder, Prototype, Singleton5
StructuralHow objects are combined into larger structuresAdapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy7
BehavioralHow objects interact and share workStrategy, Observer, Command, State, Iterator, Template Method and others11
Gang of Four pattern categories

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.

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.

ConceptScopeExampleRelation to design patterns
IdiomOne language constructResource acquisition is initialization in C++Lower level and language-specific
Design patternA few classes or modulesStrategy, Observer, AdapterThe reference level
Architectural patternA whole application or systemLayered architecture, Clean Architecture, MicroservicesHigher level; often built from many design patterns
FrameworkReusable executable codeA web or dependency injection frameworkImplements patterns rather than describing them
Anti-patternAny levelGod Object, Golden HammerA common solution that causes more harm than good
Design patterns and neighboring concepts

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.

SituationSuitable patternReason
An algorithm must be swappable at runtimeStrategyEncapsulates each variant behind one interface
Several components must react to a state changeObserverDecouples the source from its listeners
A third-party API does not match the expected interfaceAdapterTranslates one interface into another
Behavior must be added without subclass explosionDecoratorWraps objects and stacks features by composition
Object construction has many optional parametersBuilderSeparates step-by-step construction from representation
A complex subsystem needs a simple entry pointFacadeHides internal detail behind one coherent interface
Matching design problems to patterns

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.