cv.cm

Coding · Design patterns

Design patterns explained: the 23 Gang of Four patterns

7 min read

Short answer

A design pattern is a named, reusable solution to a problem that keeps appearing in software design, described at a level above concrete code. The classic catalog is the 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson and Vlissides (the "Gang of Four", GoF), which lists 23 patterns in three groups: 5 creational, 7 structural and 11 behavioral. Patterns give teams a shared vocabulary, but they are tools for specific problems, not goals in themselves.

What a design pattern is

A design pattern is not a library or a piece of code you copy. It is a description of a recurring problem, the core of a solution, and the trade-offs of using it. You still write the code yourself, adapted to your language and your program.

The GoF book describes every pattern with four essential elements:

  • Name: a word or two that lets developers talk about the design, such as "we can wrap it in a Decorator".
  • Problem: when to apply the pattern, and the situation that makes it necessary.
  • Solution: the classes or objects involved, their responsibilities and how they collaborate.
  • Consequences: the costs and benefits, such as more flexibility in exchange for more classes.

The idea comes from architecture. Christopher Alexander's book A Pattern Language (1977) described recurring solutions for buildings and towns, and the GoF authors applied the same idea to object-oriented software.

The Gang of Four book

Design Patterns: Elements of Reusable Object-Oriented Software was published by Addison-Wesley in 1994. Its examples are in C++ and Smalltalk, but the patterns apply to any object-oriented language, including Java, C#, TypeScript, Python and Kotlin.

Besides the catalog, the book argues for two general principles that run through most of the patterns:

  • Program to an interface, not an implementation. Code should depend on what an object can do, not on its concrete class.
  • Favor object composition over class inheritance. Building behavior by combining objects is usually more flexible than building deep class hierarchies.

The book also classifies each pattern by scope: class patterns use inheritance and are fixed at compile time (Factory Method, class Adapter, Interpreter, Template Method), while object patterns use composition and can change at run time (all the others).

All 23 GoF patterns by category

Creational patterns deal with how objects are created. Structural patterns deal with how classes and objects are combined into larger structures. Behavioral patterns deal with how objects communicate and share responsibilities.

PatternCategoryIntent in one line
Abstract FactoryCreationalCreate families of related objects without naming their concrete classes.
BuilderCreationalConstruct a complex object step by step, separating construction from representation.
Factory MethodCreationalLet subclasses decide which class to instantiate.
PrototypeCreationalCreate new objects by copying an existing instance.
SingletonCreationalEnsure a class has only one instance and give global access to it.
AdapterStructuralConvert one interface into another that clients expect.
BridgeStructuralSeparate an abstraction from its implementation so both can vary independently.
CompositeStructuralTreat individual objects and trees of objects in the same way.
DecoratorStructuralAdd responsibilities to an object dynamically by wrapping it.
FacadeStructuralProvide one simple interface to a complex subsystem.
FlyweightStructuralShare common state to support large numbers of small objects efficiently.
ProxyStructuralProvide a stand-in that controls access to another object.
Chain of ResponsibilityBehavioralPass a request along a chain of handlers until one handles it.
CommandBehavioralTurn a request into an object, so it can be queued, logged or undone.
InterpreterBehavioralRepresent a simple language's grammar as classes and evaluate sentences in it.
IteratorBehavioralAccess elements of a collection in order without exposing its internal structure.
MediatorBehavioralCentralize communication between objects so they do not refer to each other directly.
MementoBehavioralCapture and restore an object's state without breaking encapsulation.
ObserverBehavioralNotify all dependents automatically when an object changes.
StateBehavioralLet an object change its behavior when its internal state changes.
StrategyBehavioralMake a family of algorithms interchangeable behind one interface.
Template MethodBehavioralDefine an algorithm's skeleton and let subclasses fill in some steps.
VisitorBehavioralAdd new operations to a class hierarchy without changing those classes.

A pattern in practice: Strategy in TypeScript

Here is a small example of a pattern at work. A checkout must calculate shipping in different ways. Instead of a growing if/else chain, each rule becomes an interchangeable object behind one interface:

interface ShippingStrategy {
  cost(weightKg: number): number;
}
class FlatRate implements ShippingStrategy {
  cost(): number { return 5; }
}
class PerKilogram implements ShippingStrategy {
  cost(weightKg: number): number { return 2 * weightKg; }
}
class Checkout {
  constructor(private shipping: ShippingStrategy) {}
  setShipping(s: ShippingStrategy): void { this.shipping = s; }
  total(itemsPrice: number, weightKg: number): number {
    return itemsPrice + this.shipping.cost(weightKg);
  }
}
const checkout = new Checkout(new FlatRate());
console.log(checkout.total(40, 3)); // 45
checkout.setShipping(new PerKilogram());
console.log(checkout.total(40, 3)); // 46

In TypeScript, functions are values, so the same design can be written with less code. This is a common theme: many patterns become simpler, or disappear, in languages with first-class functions.

type Shipping = (weightKg: number) => number;
const flatRate: Shipping = () => 5;
const perKilogram: Shipping = (w) => 2 * w;
function total(itemsPrice: number, weightKg: number, shipping: Shipping): number {
  return itemsPrice + shipping(weightKg);
}
console.log(total(40, 3, flatRate));    // 45
console.log(total(40, 3, perKilogram)); // 46

How to choose a pattern

Start from the problem, never from the pattern. A pattern is worth its extra classes only when it removes a real pain in your code. These questions point to the usual candidates:

If your problem is...Consider
Code must not depend on which concrete class it createsFactory Method, Abstract Factory
A constructor has many optional parametersBuilder
You need to fit an existing class into a different interfaceAdapter
A complex library needs one simple entry pointFacade
You want to add features to objects without subclass explosionDecorator
You work with trees, such as menus, files or UI widgetsComposite, Visitor, Iterator
Many parts must react when one object changesObserver
Behavior depends on a mode or status with many branchesState
You need interchangeable algorithms or rulesStrategy
Actions must be undone, queued or loggedCommand, Memento
  1. Write the simplest code that works first.
  2. When the same change keeps hurting (for example, every new payment type edits five files), name the problem.
  3. Look up the patterns that address that problem and read their consequences.
  4. Apply the smallest version of the pattern that removes the pain, and stop there.

Criticism and limits

Design patterns are useful, but they are often misused. The common criticisms are worth knowing:

  • Overuse: adding factories, interfaces and managers "just in case" makes simple code hard to read. This is sometimes called pattern fever.
  • Missing language features: in 1996 Peter Norvig showed that 16 of the 23 GoF patterns are invisible or simpler in dynamic languages such as Lisp and Dylan. First-class functions replace Strategy and Command, and built-in iterators replace Iterator.
  • Singleton in particular is widely considered an anti-pattern when used as global state, because it hides dependencies and makes testing harder.
  • The catalog is from 1994. Later practice added other well-known patterns that are not in the book, such as dependency injection, repository, and Model-View-Controller variants.

The lasting value of the book is the vocabulary and the habit of thinking about trade-offs. Saying "this is an Adapter" tells another developer a lot in two words.

Related principles

Patterns are often explained together with the SOLID principles of object-oriented design. For example, Strategy and Decorator are direct ways to follow the open/closed principle, and Abstract Factory is a common way to apply the dependency inversion principle. Learning the principles helps you understand why a pattern is shaped the way it is.

FAQ

How many design patterns are there?

The Gang of Four book catalogs 23 patterns: 5 creational, 7 structural and 11 behavioral. Many other patterns have been described since, for example for concurrency, enterprise applications and user interfaces.

Which design patterns should I learn first?

Start with the ones you will meet most often in real code: Strategy, Observer, Factory Method, Decorator, Adapter and Facade. Then learn Builder, Composite and Command.

Are design patterns still relevant?

Yes, as a shared vocabulary and as solutions to recurring problems. Some patterns are now built into languages and frameworks, so you use them without writing the full class structure.

Do design patterns only apply to object-oriented languages?

The GoF patterns were written for object-oriented languages, but the underlying ideas appear everywhere. In functional languages many of them become simple higher-order functions.

What is the difference between a design pattern and an algorithm?

An algorithm is a precise sequence of steps that solves a computational problem, such as sorting. A design pattern describes how to organize code and responsibilities, and it is adapted to each program rather than followed step by step.

All coding tutorials