CMD Guide
HomeOO & Low-Level DesignBehavioral

Chain of Responsibility Pattern

The Chain of Responsibility pattern lets a request travel along a line of handler objects. The sender does not know which handler will deal with the request; it simply hands it to the first handler in the chain. Each handler decides one of two things: I can handle this, or I cannot, so I pass it to the next handler. The request flows down the chain until some handler processes it or the chain runs out.

This decouples the sender of a request from its receiver. Instead of one object containing a tangle of conditionals deciding who does what, responsibility is spread across small, single-purpose handlers that can be added, removed, or reordered without touching the others.

The problem it solves

Imagine a customer-support system where every query — technical, billing, or general — is processed by a single CustomerSupport class packed with branching logic. That one class grows a giant if/else if cascade, knows about every category, and must be edited every time a new kind of query appears. It is hard to test, hard to extend, and violates the Single Responsibility Principle.

Chain of Responsibility breaks that monolith into a sequence of focused handlers — TechnicalSupportHandler, BillingSupportHandler, GeneralSupportHandler — each owning exactly one category. A query enters the chain at the first handler; if that handler does not own the category, it forwards the query to its successor, and so on until the right handler processes it.

Real-world analogy

Walking into a restaurant is the same shape. The host seats you and handles seating requests; a waiter takes your order and handles service questions; the chef steps in for a custom dish; the manager handles a billing dispute. Each role handles what falls in its remit and escalates the rest. Your request moves through that line of people exactly as a request moves through a chain of handlers.

Structure

The pattern has three roles:

The client sends to the first concrete handler. If that handler can process the request it does so; otherwise it forwards to the next. This continues until the request is handled or the chain ends.

diagram
diagram

Implementation

The abstract SupportHandler holds the link to the next handler and declares handleRequest. Each concrete handler checks whether the query is its category; if so it processes it, otherwise it forwards to nextHandler. Note the forwarding line is identical in every handler — that duplication is deliberate here for clarity, but it is exactly the thing the pattern is most often gotten wrong on (see Pitfalls).

enum QueryType { TECHNICAL, BILLING, GENERAL }

abstract class SupportHandler {
  protected SupportHandler nextHandler;

  public void setNextHandler(SupportHandler next) {
    this.nextHandler = next;
  }

  public abstract void handleRequest(QueryType type, String message);
}

class TechnicalSupportHandler extends SupportHandler {
  @Override
  public void handleRequest(QueryType type, String message) {
    if (type == QueryType.TECHNICAL) {
      System.out.println("Technical Support: " + message);
    } else if (nextHandler != null) {
      nextHandler.handleRequest(type, message);
    }
  }
}

class BillingSupportHandler extends SupportHandler {
  @Override
  public void handleRequest(QueryType type, String message) {
    if (type == QueryType.BILLING) {
      System.out.println("Billing Support: " + message);
    } else if (nextHandler != null) {
      nextHandler.handleRequest(type, message);
    }
  }
}

class GeneralSupportHandler extends SupportHandler {
  @Override
  public void handleRequest(QueryType type, String message) {
    if (type == QueryType.GENERAL) {
      System.out.println("General Support: " + message);
    } else if (nextHandler != null) {
      nextHandler.handleRequest(type, message);
    }
  }
}

// Client assembles the chain
SupportHandler tech = new TechnicalSupportHandler();
SupportHandler billing = new BillingSupportHandler();
SupportHandler general = new GeneralSupportHandler();
tech.setNextHandler(billing);
billing.setNextHandler(general);

tech.handleRequest(QueryType.BILLING, "Question about my invoice.");

The fix the Pitfalls section points to is to hoist forwarding into the base class and add a terminal default so an unowned request is never silently dropped — a small final template that every handler inherits:

abstract class SupportHandler {
  protected SupportHandler nextHandler;
  public void setNextHandler(SupportHandler next) { this.nextHandler = next; }

  // final: the walk-or-forward logic is written ONCE, not copy-pasted per handler
  public final void handle(QueryType type, String message) {
    if (canHandle(type)) { doHandle(message); return; }
    if (nextHandler != null) { nextHandler.handle(type, message); return; }
    throw new UnhandledRequestException(type);   // terminal: never a silent drop
  }
  protected abstract boolean canHandle(QueryType type);
  protected abstract void doHandle(String message);
}

Now a concrete handler only declares what it owns (canHandle) and what it does (doHandle); the forwarding and the end-of-chain rejection are impossible to get wrong because no handler writes them.

The same design in Go uses a slice of small handlers or a linked successor field rather than inheritance:

type QueryType int
const (
  Technical QueryType = iota
  Billing
  General
)

type Handler interface {
  Handle(t QueryType, msg string)
  SetNext(h Handler)
}

type base struct{ next Handler }
func (b *base) SetNext(h Handler) { b.next = h }
func (b *base) forward(t QueryType, msg string) {
  if b.next != nil {
    b.next.Handle(t, msg)
  }
}

type TechnicalHandler struct{ base }
func (h *TechnicalHandler) Handle(t QueryType, msg string) {
  if t == Technical {
    fmt.Println("Technical Support:", msg)
    return
  }
  h.forward(t, msg)
}

type BillingHandler struct{ base }
func (h *BillingHandler) Handle(t QueryType, msg string) {
  if t == Billing {
    fmt.Println("Billing Support:", msg)
    return
  }
  h.forward(t, msg)
}

// Client
tech := &TechnicalHandler{}
billing := &BillingHandler{}
tech.SetNext(billing)
tech.Handle(Billing, "Question about my invoice.")

Pulling the forward step into a shared base (Go) or a template method on SupportHandler (Java) removes the copy-pasted nextHandler != null line from every handler.

Variants

Two design choices vary independently of one another.

Pure vs. impure handling

In a pure chain exactly one handler processes a request and forwarding stops there. In an impure (pipeline) chain, a handler may do part of the work and still forward — a logging handler logs and passes on, an auth handler validates and passes on. Servlet filters and HTTP middleware are pipeline chains: every link runs.

Explicit vs. implicit successor

An explicit successor is a next field each handler holds and calls (shown above). An implicit chain stores handlers in a list and an external loop walks them, so handlers do not know about each other at all — easier to reorder, but the per-handler “stop or continue” decision now has to be signalled by a return value instead of by simply not forwarding.

Pitfalls

These are the failure modes to watch for — the forward-references above point here.

When to use it — and when not to

This is the decision the pattern actually turns on, set against the two alternatives people reach for first.

Reach for Chain of Responsibility when

Prefer a switch / if-else instead when

The categories are fixed, few, and known at compile time, and there is no notion of escalation or fall-through. A single conditional is the least machinery and keeps all the logic visible in one place. Only graduate to a chain once that conditional starts attracting unrelated responsibilities or changing on every new case. Do not introduce a chain purely to avoid an if.

Prefer Strategy / a map lookup instead when

Each request maps to exactly one handler by a key, with no ordered trying and no fall-through. A Map<Key, Handler> gives O(1) direct dispatch and stays open for extension, whereas a chain walks O(n) links to reach the same handler. Choose the map when dispatch is a clean one-to-one lookup; choose the chain only when “ask each in turn until someone takes it” is genuinely the behaviour you need. If you find yourself wanting both keyed dispatch and ordered fallback, that is the signal a chain (not a map) is the right tool.

diagram
diagram

Source

Adapted and rewritten from the project lesson Chain of Responsibility Pattern (site/oo-low-level-design/behavioral/001-chain-of-responsibility-pattern.html), with the classic definition from Gamma, Helm, Johnson & Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (1994).

🔨 Practice this hands-on — Design a Logging Framework →
Attempt it from an empty file, break it to feel the failure, then defend it under pushback.
🤖 Don't fully get this? Learn it with Claude

Stuck on Chain of Responsibility Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Chain of Responsibility Pattern** (OO & Low-Level Design) and want to truly understand it. Explain Chain of Responsibility Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Chain of Responsibility Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Chain of Responsibility Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Chain of Responsibility Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes