Chain of Responsibility
Pass a request along a chain of handlers. Each handler decides to process the request or pass it to the next handler. Decouples the sender from the receiver.
Chain of Responsibility = airport security checkpoints. Your bag goes through ID check, then X-ray, then customs. Each station either stops you or waves you through to the next. Add a new scanner? Just insert it in the line.
Each handler holds a reference to the next handler. The client sends a request to the first handler; handlers process or forward. Examples: HTTP middleware/filter chains (authentication → authorization → rate limiting → logging → handler); Java servlet filter chain; logging levels (ERROR handler processes errors; WARNING handler processes warnings or passes up; INFO passes to DEBUG); GUI event bubbling (child component handles click or bubbles to parent).
Chain of Responsibility provides flexibility in assembling pipelines but has a debugging challenge: tracing which handler processed a request requires logging at each node. Two variants: (1) one handler processes (classic CoR — request stops at the first capable handler); (2) all handlers process (pipeline — used in middleware). For pipelines, the chain is better modeled as a list of handlers iterated by a dispatcher, not a recursive linked list. The pattern is directly implemented in Java Servlet filters, Node.js middleware (Express.js app.use()), and Spring Security filter chains. Each middleware function calls next() to continue the chain or returns a response to short-circuit.
Chain of Responsibility is the pattern behind every middleware stack I've built. In an API gateway: the request passes through AuthenticationFilter, AuthorizationFilter, RateLimitFilter, RequestValidationFilter, and finally the business handler. Each filter either short-circuits (returns 401/429) or calls chain.doFilter(). Adding a new concern (IP allowlist check) is adding one filter class and inserting it in the chain — no existing filters change. I distinguish classic CoR (first capable handler wins) from pipeline (all handlers execute) — middleware is almost always a pipeline.
Classic CoR has no guarantee that any handler processes the request — if no handler matches, the request is dropped silently. Always have a default handler or explicit error for unhandled requests.