Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Chain of Responsibility
LLD

Chain of Responsibility

Learn how to pass requests through a chain of handlers until one handles the request.

Chain of Responsibility: Request Pipeline

The Problem It Solves

An incoming request needs a variable sequence of checks and handlers: auth, rate limit, validation, caching, business handling. Hardcoding that sequence in the caller couples it to every handler; adding one check edits core flow. Chain of Responsibility lines up handlers, each deciding: handle this, or pass it to the next — the caller knows only the chain’s head.

 THE ESCALATION MODEL

 request ──► AuthHandler ──► RateLimitHandler ──► Validation ──► BusinessHandler
                │  pass if ok       │                 │               │
                │                   │ 429 → stop      │ bad → stop    │ handles → stop
        401 → stop (each link either CONSUMES or FORWARDS)
 caller wires: auth.setNext(rateLimit).setNext(validation)...

The support-desk analogy maps exactly: L1 answers if qualified, else escalates to L2, else L3. Each tier’s knowledge is local; adding “L0 bot tier” inserts one link without telling the others.

Two Wiring Styles

// CLASSIC — linked objects with explicit next pointer:
abstract class Handler {
    private Handler next;
    Handler setNext(Handler n) { this.next = n; return n; }   // fluent wiring
    void handle(Request r) {
        if (!process(r) && next != null) next.handle(r);      // false = not consumed
    }
    protected abstract boolean process(Request r);
}

// LIST-BASED — pipeline as ordered collection (frameworks prefer this):
class Pipeline {
    private final List<Middleware> middlewares;
    Response execute(Request r) {
        Response resp = Response.ok();
        for (var mw : middlewares) {          // order = list order, explicit
            resp = mw.apply(r, resp);
            if (resp.isTerminal()) break;
        }
        return resp;
    }
}

List-based wins for configurability (order from config, per-route pipelines); classic form wins when handlers themselves decide routing.

Real-World Sightings

  • Servlet filters / Spring interceptors — literally called filter chains.
  • Netty channel pipeline, Express.js middleware, Nginx processing phases.
  • Exception matching up JDK call stacks is conceptually the same walk.

Design Rules That Prevent Production Pain

RuleWhy
Guarantee a terminal handlerChains that fall off the end silently drop requests
Make ordering explicit & documented”Auth before rate-limit” bugs are order-dependent by nature
Cap chain length / detect cyclesMis-wired a.setNext(b); b.setNext(a) = infinite loop
Decide consumption semantics up front”handled” vs “enriched-and-passed” are different contracts

vs Decorator & Strategy

PatternQuestion it answers
ChainWhich handler processes this request? (dynamic selection by walking)
DecoratorAll layers wrap in fixed stack (no selection — everyone runs)
StrategyCaller picks algorithm directly

Chain ≈ decorator where each node may terminate traversal.

Trade-offs

  • Gains: caller decoupled from handler identity; runtime-reconfigurable sequences; single-responsibility links.
  • Costs: request can vanish silently if no link consumes it; debugging traversals needs logging at each hop; deep chains add per-hop overhead (trivial locally, real at framework scale).

Interview Framing

  • Asked to design rate limiting/auth middleware, drawing the pipeline with terminal-guarantee + explicit ordering covers both pattern and production axes.

My Private Notes

Notes are auto-saved locally to this device.