The Chronicle 8 min read
Design Patterns

Design Patterns Every Software Engineer Must Know

A practical guide to the 23 Gang of Four patterns with real-world examples

L
LLDCanvas Team·Engineering
July 24, 2026
8 min read 92 views 0 likes
Jump to section

A design pattern is a reusable, named solution to a problem that recurs in a particular context of object-oriented design. Patterns are not code you copy-paste - they are a shared vocabulary. When one engineer says "just make it a Strategy" instead of explaining a five-step refactor, the whole team saves time. That shared vocabulary is exactly why patterns show up so often in low-level design (LLD) interviews: they let you communicate a design decision in one word instead of a paragraph.

The canonical reference is the 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides - collectively known as the Gang of Four (GoF). It catalogs 23 patterns split into three families: Creational (how objects get created), Structural (how objects and classes are composed into larger structures), and Behavioral (how objects communicate and share responsibility). This guide covers all 23, with working Java for the ten you are most likely to actually write in an interview or a real codebase.

Practice on a canvas: LLDCanvas's editor ships with pre-wired templates for every GoF pattern, so you can trace the class relationships instead of just reading about them.

01Creational Patterns

Creational patterns abstract away the details of how and when objects are instantiated, so client code depends on interfaces rather than concrete constructors.

Singleton

Intent: Guarantee that a class has exactly one instance and provide a single global point of access to it. Used in: loggers, configuration managers, connection pools, thread pools - anywhere a shared, expensive-to-create resource must not be duplicated. The main interview trap is thread safety: a naive lazy-initialized singleton can produce two instances under concurrent access, which is why double-checked locking (or an enum, or a static holder class) is expected in a serious answer.

java
public class Logger {
    private static volatile Logger instance;

    private Logger() {}

    public static Logger getInstance() {
        if (instance == null) {
            synchronized (Logger.class) {
                if (instance == null) {
                    instance = new Logger();
                }
            }
        }
        return instance;
    }

    public void log(String message) {
        System.out.println("[LOG] " + message);
    }
}

Factory Method

Intent: Define an interface for creating an object, but let subclasses decide which concrete class to instantiate. Used in: payment processors that vary by provider, document parsers that vary by file type, UI toolkits that render differently per OS. The caller only ever talks to the abstract type, so adding a new variant means adding a new subclass - no existing code changes, which is the whole point of the open-closed principle.

java
public abstract class PaymentProcessor {
    public abstract Payment createPayment(double amount);

    public void process(double amount) {
        Payment payment = createPayment(amount);
        payment.validate();
        payment.charge();
        payment.sendReceipt();
    }
}

public class StripeProcessor extends PaymentProcessor {
    @Override
    public Payment createPayment(double amount) {
        return new StripePayment(amount);
    }
}

public class PayPalProcessor extends PaymentProcessor {
    @Override
    public Payment createPayment(double amount) {
        return new PayPalPayment(amount);
    }
}

Builder

Intent: Separate the construction of a complex object from its representation, so the same construction process can build different representations - and so callers avoid a constructor with ten optional parameters. Used in: HTTP client requests, SQL query builders, immutable domain objects with many optional fields. A fluent builder also makes call sites self-documenting, since every argument is named.

java
HttpRequest request = new HttpRequest.Builder("GET", "https://api.example.com")
    .header("Authorization", "Bearer token123")
    .timeout(Duration.ofSeconds(30))
    .retry(3)
    .build();

A few more creational patterns

  • Abstract Factory: Produces families of related objects (for example, a WindowsFactory that creates matching WindowsButton and WindowsCheckbox objects) without specifying their concrete classes.
  • Prototype: Creates new objects by cloning an existing, fully-configured instance instead of building one from scratch - useful when object construction is expensive or configuration-heavy.

02Structural Patterns

Structural patterns describe how classes and objects are composed into larger structures while keeping those structures flexible and efficient.

Decorator

Intent: Attach additional responsibilities to an object dynamically, as a flexible alternative to subclassing. Used in: Java I/O streams (BufferedReader wrapping a FileReader), UI components (scrollable, bordered widgets), and middleware that wraps a request handler with logging, caching, or auth checks. Each decorator implements the same interface as the object it wraps, so decorators stack transparently.

java
public interface Coffee {
    double cost();
}

public class SimpleCoffee implements Coffee {
    public double cost() { return 1.00; }
}

public abstract class CoffeeDecorator implements Coffee {
    protected final Coffee wrapped;
    protected CoffeeDecorator(Coffee wrapped) { this.wrapped = wrapped; }
}

public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee wrapped) { super(wrapped); }
    public double cost() { return wrapped.cost() + 0.30; }
}

public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee wrapped) { super(wrapped); }
    public double cost() { return wrapped.cost() + 0.20; }
}

// Usage
Coffee coffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
System.out.println(coffee.cost()); // 1.50

Facade

Intent: Provide a single, simplified interface to a complex subsystem of many interacting classes. Used in: e-commerce checkout flows, where one method call quietly coordinates inventory, payment, shipping, and notifications. A facade does not hide the subsystem's classes from callers who need finer control - it just gives everyone else a one-line entry point for the common case.

java
public class OrderFacade {
    private final InventoryService inventory;
    private final PaymentService payment;
    private final ShippingService shipping;
    private final NotificationService notifications;

    public OrderFacade(InventoryService inventory, PaymentService payment,
                        ShippingService shipping, NotificationService notifications) {
        this.inventory = inventory;
        this.payment = payment;
        this.shipping = shipping;
        this.notifications = notifications;
    }

    public void placeOrder(Order order) {
        inventory.reserve(order.getItems());
        payment.charge(order.getCustomer(), order.getTotal());
        shipping.schedule(order);
        notifications.sendConfirmation(order.getCustomer());
    }
}

Composite

Intent: Compose objects into tree structures and let clients treat individual objects and compositions of objects uniformly. Used in: file systems (files and folders), UI component trees, and org charts. Both the leaf and the container implement the same interface, so code that walks the tree never needs to check "is this a file or a folder?".

java
public interface FileSystemNode {
    long size();
}

public class File implements FileSystemNode {
    private final long sizeInBytes;
    public File(long sizeInBytes) { this.sizeInBytes = sizeInBytes; }
    public long size() { return sizeInBytes; }
}

public class Folder implements FileSystemNode {
    private final List<FileSystemNode> children = new ArrayList<>();

    public void add(FileSystemNode node) { children.add(node); }

    public long size() {
        return children.stream().mapToLong(FileSystemNode::size).sum();
    }
}

A few more structural patterns

  • Adapter: Converts the interface of a class into another interface clients expect - for example, wrapping a legacy XmlParser behind a JsonParser-shaped interface.
  • Bridge: Decouples an abstraction from its implementation so the two can vary independently, such as a Shape hierarchy that can render through different DrawingAPI implementations.
  • Flyweight: Shares fine-grained objects to support large numbers of them efficiently - the classic example is caching glyph objects when rendering text.
  • Proxy: Provides a stand-in for another object to control access to it, adding lazy loading, caching, access control, or logging without changing the real object.

03Behavioral Patterns

Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects - how they communicate and stay loosely coupled while doing so.

Observer

Intent: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified automatically. Used in: stock price feeds, event-driven UIs, pub/sub messaging systems. The subject only knows about a generic observer interface, so new subscriber types can be added without touching the subject.

java
public interface StockObserver {
    void onPriceChange(String symbol, double price);
}

public class StockMarket {
    private final Map<String, List<StockObserver>> observers = new HashMap<>();

    public void subscribe(String symbol, StockObserver observer) {
        observers.computeIfAbsent(symbol, k -> new ArrayList<>()).add(observer);
    }

    public void updatePrice(String symbol, double price) {
        observers.getOrDefault(symbol, List.of())
                 .forEach(o -> o.onPriceChange(symbol, price));
    }
}

Strategy

Intent: Define a family of interchangeable algorithms, encapsulate each one, and let the client swap them at runtime. Used in: payment methods at checkout, sorting/compression algorithms chosen by data size, route-calculation engines that switch between fastest-route and shortest-route logic. The context class holds a reference to a strategy interface and never has an if/else chain over algorithm types.

java
public interface PaymentStrategy {
    void pay(double amount);
}

public class CreditCardStrategy implements PaymentStrategy {
    public void pay(double amount) { System.out.println("Charged $" + amount + " to credit card"); }
}

public class UpiStrategy implements PaymentStrategy {
    public void pay(double amount) { System.out.println("Paid $" + amount + " via UPI"); }
}

public class Checkout {
    private PaymentStrategy strategy;

    public void setStrategy(PaymentStrategy strategy) { this.strategy = strategy; }

    public void checkout(double amount) { strategy.pay(amount); }
}

Command

Intent: Encapsulate a request as a standalone object, so requests can be queued, logged, parameterized, and - crucially - undone. Used in: text editor undo/redo stacks, remote controls, task queues, and transactional operations that need rollback. Each command knows how to execute() and how to undo(), and an invoker just holds a history of executed commands.

java
public interface Command {
    void execute();
    void undo();
}

public class LightOnCommand implements Command {
    private final Light light;
    public LightOnCommand(Light light) { this.light = light; }
    public void execute() { light.turnOn(); }
    public void undo() { light.turnOff(); }
}

public class RemoteControl {
    private final Deque<Command> history = new ArrayDeque<>();

    public void submit(Command command) {
        command.execute();
        history.push(command);
    }

    public void undoLast() {
        if (!history.isEmpty()) history.pop().undo();
    }
}

State

Intent: Allow an object to alter its behavior when its internal state changes, so it appears to change its class. Used in: ATMs, vending machines, and order lifecycle management (PLACED -> PAID -> SHIPPED -> DELIVERED). Instead of a single class riddled with state-flag conditionals, each state is its own class that knows exactly which transitions are legal from there.

java
public interface OrderState {
    void next(OrderContext context);
}

public class PlacedState implements OrderState {
    public void next(OrderContext context) {
        System.out.println("Payment received, order is now PAID");
        context.setState(new PaidState());
    }
}

public class PaidState implements OrderState {
    public void next(OrderContext context) {
        System.out.println("Order shipped, now SHIPPED");
        context.setState(new ShippedState());
    }
}

public class OrderContext {
    private OrderState state = new PlacedState();
    public void setState(OrderState state) { this.state = state; }
    public void advance() { state.next(this); }
}

A few more behavioral patterns

  • Chain of Responsibility: Passes a request along a chain of handlers until one of them handles it - the model behind HTTP middleware and authentication pipelines.
  • Template Method: Defines the skeleton of an algorithm in a base class and lets subclasses override individual steps without changing the overall structure.
  • Iterator: Provides a way to access elements of a collection sequentially without exposing its underlying representation - what every for-each loop relies on.
  • Mediator: Centralizes complex communication between a set of objects into one mediator object, so those objects no longer reference each other directly (common in chat rooms and air-traffic-control style systems).
  • Memento: Captures and externalizes an object's internal state so it can be restored later, without violating encapsulation - the basis of undo history and save/restore snapshots.
  • Visitor: Lets you add new operations to a group of related classes without modifying them, by having each class accept a visitor object.
  • Interpreter: Defines a representation for a language's grammar along with an interpreter that uses it to evaluate sentences - the basis of rule engines and simple expression parsers.

See patterns in real problems: browse LLDCanvas's interview question bank to see which pattern fits problems like Parking Lot, Elevator System, and Rate Limiter.

Need a scannable review pass? The Design Patterns Cheat Sheet puts all 23 patterns, their intent, and a real-world example on one page.

04Conclusion

Twenty-three patterns is a lot to memorize, but you do not need all of them equally. In interviews and in production code, a small subset does most of the work: Singleton, Factory Method, Builder, Decorator, Facade, Observer, Strategy, Command, and State cover the overwhelming majority of LLD problems you will encounter, from parking lots to rate limiters to order-management systems. Learn those nine deeply - their intent, their trade-offs, and how to code them cold - and treat the remaining fourteen as a recognition vocabulary you can look up when the situation calls for them.

The fastest way to make a pattern stick is to apply it to a problem you actually care about solving, not to memorize its class diagram. Pick a pattern, pick a real system, and build it.

Frequently Asked Questions

AThe GoF book defines 23 patterns. These form the foundation; there are many more beyond GoF.

L

LLDCanvas Team

Engineering at LLDCanvas

Ready to practice?

Turn reading into results

110+ LLD interview questions, a live canvas, and timed Interview Mode.

Reader’s Notes (0)

Sign in to join the discussion

Sign in

Loading notes…