Skip to main content
Design patterns are reusable solutions to commonly occurring problems in software design. The Gang of Four (GoF) catalogued 23 patterns across three categories: creational patterns that abstract and encapsulate object creation, structural patterns that describe how classes and objects are composed into larger structures, and behavioral patterns that focus on communication and responsibility among objects. Understanding these patterns makes your code more flexible, maintainable, and easier to extend—because you change creation logic in one factory rather than hunting down every instantiation site, restructure relationships without rewriting consuming code, and swap behaviors at runtime rather than through conditional branches.

Creational Patterns

Creational patterns separate the concern of how objects come into existence from how they are used. This separation lets you change the creation strategy—from a single shared instance, to a family of related objects, to a fully configured complex object—without touching the code that consumes those objects.

Factory Method

The Factory Method pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. You use it when you need to decouple object creation from the code that uses the object, and when you want to support different object types without modifying calling code. Example: A car factory creates different vehicle types based on a string parameter, so the rest of your application never calls new Sedan() or new SUV() directly.
interface Car {
    void drive();
}

class Sedan implements Car {
    @Override
    public void drive() {
        System.out.println("Driving a sedan.");
    }
}

class SUV implements Car {
    @Override
    public void drive() {
        System.out.println("Driving an SUV.");
    }
}

class CarFactory {
    public Car createCar(String type) {
        if ("sedan".equals(type)) {
            return new Sedan();
        } else if ("suv".equals(type)) {
            return new SUV();
        }
        return null;
    }
}
Adding a new vehicle type means adding a class and one branch in the factory—no change to business logic that calls factory.createCar(...).

Abstract Factory

The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. Think of it as a factory where the factory itself is abstracted: each concrete factory implementation produces a coordinated set of products. Key insight: Each concrete factory creates a consistent suite of objects. A ModernFurnitureFactory produces modern tables and modern chairs together; a ClassicalFurnitureFactory produces the classical variants. You swap the entire product family by swapping the factory.
interface FurnitureFactory {
    Table createTable();
    Chair createChair();
}

class ModernFurnitureFactory implements FurnitureFactory {
    @Override
    public Table createTable() { return new ModernTable(); }
    @Override
    public Chair createChair() { return new ModernChair(); }
}

class ClassicalFurnitureFactory implements FurnitureFactory {
    @Override
    public Table createTable() { return new ClassicalTable(); }
    @Override
    public Chair createChair() { return new ClassicalChair(); }
}
GUI toolkits use this pattern to provide platform-specific widgets (Windows buttons, macOS buttons) while keeping application code platform-agnostic.

Builder

The Builder pattern separates the construction of a complex object from its representation, so the same construction process can produce different representations. You use it when an object has many optional configuration parameters that would otherwise lead to telescoping constructors. Example: Building a Computer with optional CPU, memory, and storage choices using a fluent builder API:
class Computer {
    private String cpu;
    private String memory;
    private String hardDisk;

    public void setCpu(String cpu) { this.cpu = cpu; }
    public void setMemory(String memory) { this.memory = memory; }
    public void setHardDisk(String hardDisk) { this.hardDisk = hardDisk; }

    @Override
    public String toString() {
        return "Computer{cpu='" + cpu + "', memory='" + memory
                + "', hardDisk='" + hardDisk + "'}";
    }
}

class ComputerBuilder {
    private Computer computer = new Computer();

    public ComputerBuilder buildCpu(String cpu) {
        computer.setCpu(cpu);
        return this;
    }
    public ComputerBuilder buildMemory(String memory) {
        computer.setMemory(memory);
        return this;
    }
    public ComputerBuilder buildHardDisk(String hardDisk) {
        computer.setHardDisk(hardDisk);
        return this;
    }
    public Computer build() {
        return computer;
    }
}

// Usage
Computer pc = new ComputerBuilder()
    .buildCpu("Intel i9")
    .buildMemory("32GB")
    .buildHardDisk("1TB SSD")
    .build();
The caller never interacts with partially constructed state, and you can easily create different configurations without overloaded constructors.

Singleton

The Singleton pattern ensures a class has exactly one instance and provides a global access point to it. You use it for shared resources like database connection pools, configuration registries, or logging systems where multiple instances would cause resource waste or inconsistency. Thread-safe lazy initialization (double-checked locking):
class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
Eager initialization (simpler, safe if instantiation cost is low):
class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}
Be cautious with Singleton in multi-threaded environments—always use volatile and double-checked locking, or the eager initialization variant above.

Prototype

The Prototype pattern creates new objects by cloning an existing instance. You use it when object creation is expensive (e.g., involves a database query or heavy computation), and a copy with slight modifications is cheaper than building from scratch. In Java, implement Cloneable and override clone(); in Go, return a copy of the struct.

Structural Patterns

Structural patterns describe how classes and objects are assembled to form larger, more complex structures. They enable you to change the composition of objects at runtime, add responsibilities without subclassing, and wrap incompatible interfaces—all without breaking existing code.

Adapter

The Adapter pattern converts one interface into another that a client expects, making otherwise incompatible classes work together. You use it most often when integrating legacy code with a new system. Example: An old printer only speaks printOldFormat(), but your new system calls printNewFormat(). An adapter bridges the gap:
class OldPrinter {
    public void printOldFormat(String data) {
        System.out.println("Printing in old format: " + data);
    }
}

interface NewComputer {
    void printNewFormat(String data);
}

class PrinterAdapter implements NewComputer {
    private OldPrinter oldPrinter;

    public PrinterAdapter(OldPrinter oldPrinter) {
        this.oldPrinter = oldPrinter;
    }

    @Override
    public void printNewFormat(String data) {
        String convertedData = "Converted: " + data;
        oldPrinter.printOldFormat(convertedData);
    }
}
The rest of your system uses NewComputer exclusively; the adapter handles translation in one place.

Decorator

The Decorator pattern attaches additional responsibilities to an object dynamically. It extends behavior through composition rather than inheritance, which means you can mix and stack decorators at runtime without creating an explosion of subclasses. It follows the open/closed principle: open for extension, closed for modification. The four roles are: Component (interface), ConcreteComponent (base object), Decorator (abstract wrapper holding a reference to a Component), and ConcreteDecorator (adds the actual new behavior).
interface Coffee {
    String getDescription();
    double cost();
}

class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() { return "Simple coffee"; }
    @Override
    public double cost() { return 5.0; }
}

abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }

    @Override
    public String getDescription() { return decoratedCoffee.getDescription(); }
    @Override
    public double cost() { return decoratedCoffee.cost(); }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) { super(coffee); }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", milk";
    }
    @Override
    public double cost() { return decoratedCoffee.cost() + 2.0; }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) { super(coffee); }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + ", sugar";
    }
    @Override
    public double cost() { return decoratedCoffee.cost() + 1.0; }
}

// Stack decorators at runtime
Coffee order = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
// → "Simple coffee, milk, sugar" at $8.0
Java’s InputStream hierarchy (e.g., BufferedInputStream wrapping FileInputStream) is a canonical real-world example.

Proxy

The Proxy pattern provides a surrogate or placeholder for another object to control access to it. You use it for lazy initialization (don’t load a heavy resource until first use), access control, caching, and logging. Example: A ProxyImage defers loading from disk until display() is actually called:
interface Image {
    void display();
}

class RealImage implements Image {
    private String fileName;

    public RealImage(String fileName) {
        this.fileName = fileName;
        System.out.println("Loading " + fileName); // expensive
    }

    @Override
    public void display() {
        System.out.println("Displaying " + fileName);
    }
}

class ProxyImage implements Image {
    private RealImage realImage;
    private String fileName;

    public ProxyImage(String fileName) {
        this.fileName = fileName; // no disk I/O yet
    }

    @Override
    public void display() {
        if (realImage == null) {
            realImage = new RealImage(fileName); // load on first access
        }
        realImage.display();
    }
}

Facade

The Facade pattern provides a simplified, unified interface to a complex subsystem. Your clients interact with the facade rather than with the dozens of classes underneath. Common examples include a HomeTheaterFacade that wraps amplifier, projector, DVD player, and lights behind a single watchMovie() call, or a service layer that hides database, cache, and external API calls from your controller.

Composite

The Composite pattern composes objects into tree structures to represent part-whole hierarchies. It lets you treat individual objects and compositions uniformly—clients call the same interface whether they hold a leaf or a branch.
interface FileSystemComponent {
    void display();
}

class File implements FileSystemComponent {
    private String name;
    public File(String name) { this.name = name; }

    @Override
    public void display() { System.out.println("File: " + name); }
}

class Folder implements FileSystemComponent {
    private String name;
    private List<FileSystemComponent> children = new ArrayList<>();

    public Folder(String name) { this.name = name; }
    public void add(FileSystemComponent c) { children.add(c); }

    @Override
    public void display() {
        System.out.println("Folder: " + name);
        for (FileSystemComponent c : children) c.display();
    }
}
File systems, UI component trees, and organizational hierarchies all fit this pattern.

Bridge

The Bridge pattern decouples an abstraction from its implementation so the two can vary independently. Instead of inheriting a color into every shape subclass (creating an M×N class explosion), you inject the color implementation as a dependency:
interface Color { void fill(); }
class RedColor implements Color {
    @Override public void fill() { System.out.println("red color."); }
}
class BlueColor implements Color {
    @Override public void fill() { System.out.println("blue color."); }
}

interface Shape { void draw(); }
class Circle implements Shape {
    private Color color;
    public Circle(Color color) { this.color = color; }
    @Override
    public void draw() {
        System.out.print("Drawing a circle with ");
        color.fill();
    }
}
Adding a new shape or a new color requires one new class each—not M+N new classes.

Flyweight

The Flyweight pattern uses sharing to support a large number of fine-grained objects efficiently. You split object state into intrinsic (shared, immutable) and extrinsic (context-specific, passed in at use time). A text editor storing per-character font objects is the canonical example: instead of millions of Character objects each holding a font reference, you share a pool of flyweight font objects.

Behavioral Patterns

Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects. They describe not just patterns of objects and classes but also the patterns of communication between them.

Observer

The Observer pattern defines a one-to-many dependency so that when one object changes state, all its dependents are notified and updated automatically. You use it for event systems, pub/sub messaging, and any situation where multiple components need to react to state changes.
interface WeatherSubject {
    void registerObserver(WeatherObserver o);
    void removeObserver(WeatherObserver o);
    void notifyObservers();
}

class WeatherData implements WeatherSubject {
    private List<WeatherObserver> observers = new ArrayList<>();
    private double temperature;
    private double humidity;

    public void setMeasurements(double temp, double humidity) {
        this.temperature = temp;
        this.humidity = humidity;
        notifyObservers();
    }

    @Override
    public void registerObserver(WeatherObserver o) { observers.add(o); }
    @Override
    public void removeObserver(WeatherObserver o) { observers.remove(o); }
    @Override
    public void notifyObservers() {
        for (WeatherObserver o : observers) o.update(temperature, humidity);
    }
}

interface WeatherObserver {
    void update(double temperature, double humidity);
}

class CurrentConditionsDisplay implements WeatherObserver {
    @Override
    public void update(double temperature, double humidity) {
        System.out.println("Temp: " + temperature + "°C, Humidity: " + humidity + "%");
    }
}
Java’s java.util.EventListener, reactive streams, and message brokers like Kafka all implement variants of this pattern.

Strategy

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. The algorithm varies independently from the clients that use it. You use it to replace conditional logic (if/switch on type) with polymorphism.
interface CheckoutStrategy {
    double calculateTotal(double price);
}

class FullReductionStrategy implements CheckoutStrategy {
    @Override
    public double calculateTotal(double price) {
        return price >= 100 ? price - 20 : price;
    }
}

class DiscountStrategy implements CheckoutStrategy {
    @Override
    public double calculateTotal(double price) {
        return price * 0.8;
    }
}

class ShoppingCart {
    private CheckoutStrategy strategy;

    public ShoppingCart(CheckoutStrategy strategy) {
        this.strategy = strategy;
    }
    public double checkout(double price) {
        return strategy.calculateTotal(price);
    }
}

// Swap strategy at runtime
ShoppingCart cart = new ShoppingCart(new DiscountStrategy());
cart.checkout(120.0); // → 96.0

Command

The Command pattern encapsulates a request as an object, letting you parameterize clients with different requests, queue or log requests, and support undoable operations. Each command object implements an execute() method; an invoker stores and triggers commands without knowing their implementation. Text editor undo/redo systems and transaction log replay are classic use cases.

Iterator

The Iterator pattern provides a sequential way to access elements of a collection without exposing its internal representation. In Java, java.util.Iterator is the canonical implementation. You use it to traverse lists, trees, graphs, and streams in a uniform way regardless of the underlying data structure.

Template Method

The Template Method pattern defines the skeleton of an algorithm in a base class, deferring some steps to subclasses. Subclasses can override specific steps without changing the algorithm’s structure. A data-processing pipeline might define readData(), processData(), and writeData() in a template—concrete subclasses provide each step for CSV, JSON, or database sources.

State

The State pattern allows an object to alter its behavior when its internal state changes. The object appears to change its class. Instead of large if/switch statements checking a status field, you represent each state as a class and delegate behavior to the current state object. An order management system with states like PendingPayment, Processing, Shipped, and Delivered is a good fit.

Chain of Responsibility

The Chain of Responsibility pattern passes a request along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain. Adding a new handling step means inserting a new handler node without modifying existing handlers. HTTP middleware pipelines and logging level filters are everyday examples.

Visitor

The Visitor pattern lets you add operations to an object structure without modifying the objects themselves. You define a Visitor interface with a visit() method for each element type, and each element calls visitor.visit(this). This is useful when you have a stable object hierarchy but frequently add new operations—for example, code analysis tools that traverse an AST.

Mediator

The Mediator pattern reduces chaotic dependencies between objects by routing all communication through a central mediator. Objects no longer refer to each other directly; they communicate via the mediator. This turns an M×N communication graph into M+N relationships. Chat room servers and air traffic control systems are classic examples.

Memento

The Memento pattern captures and externalizes an object’s internal state so the object can be restored to that state later, without violating encapsulation. The originator creates a memento, a caretaker stores it, and the originator can restore from it. Text editors with multi-level undo use this pattern.

Interpreter

The Interpreter pattern defines a grammar for a language and provides an interpreter to deal with that grammar. Each grammar rule becomes a class. You use it for DSLs, regular expression engines, and SQL-like query parsers. It is rarely used in production because parser combinator libraries and grammar tools are usually a better choice for complex languages.
The three most commonly used patterns in backend service development are Singleton, Factory Method, and Observer. Among structural patterns, Proxy, Decorator, and Adapter are the most frequently encountered. Start with these before diving into the less common ones like Flyweight or Interpreter.