Chain of Responsibility — passing a request along a line of handlers

When several different things might need to handle a request, and which one actually should depends on the request itself, hardcoding that decision into one big if/elif chain couples every handler together — Chain of Responsibility lets each handler decide for itself, independently, whether to handle it or pass it on.

Intermediate

3 min read

The problem: one big dispatcher that has to know about every handler

def handle_request(request, level):
    if level == "info":
        log_info(request)
    elif level == "warning":
        log_warning(request)
    elif level == "error":
        send_alert(request)
        log_error(request)
    else:
        raise ValueError(f"Unknown level: {level}")

This dispatcher function has to know about every possible handler and every possible level up front — adding a new level means editing this one function directly, and the function itself becomes a single point coupling every handler together, even though log_info and send_alert have nothing to do with each other and don't need to know the other exists.

The fix: each handler decides independently whether to handle it, or pass it along

class Handler:
    def __init__(self):
        self._next = None
    def set_next(self, handler):
        self._next = handler
        return handler
    def handle(self, request):
        if self._next:
            return self._next.handle(request)  # not my job — pass it down the chain
        return None  # end of the chain, nobody handled it
 
class InfoHandler(Handler):
    def handle(self, request):
        if request.level == "info":
            return log_info(request)
        return super().handle(request)  # not info — try the NEXT handler
 
class WarningHandler(Handler):
    def handle(self, request):
        if request.level == "warning":
            return log_warning(request)
        return super().handle(request)
 
class ErrorHandler(Handler):
    def handle(self, request):
        if request.level == "error":
            send_alert(request)
            return log_error(request)
        return super().handle(request)
 
info = InfoHandler()
info.set_next(WarningHandler()).set_next(ErrorHandler())
info.handle(request)  # tries InfoHandler, then Warning, then Error — whichever ACTUALLY matches

Each handler checks only whether it should handle this specific request — if not, it forwards to self._next and doesn't need to know or care what that next handler actually is, or how many more handlers come after it. InfoHandler has zero knowledge of ErrorHandler's existence; the chain is assembled externally (set_next), not hardcoded inside any single handler.

Adding a new handler doesn't touch any existing handler's code

class DebugHandler(Handler):
    def handle(self, request):
        if request.level == "debug":
            return log_debug(request)
        return super().handle(request)
 
# Insert it into the chain wherever makes sense — no OTHER handler needs editing
debug = DebugHandler()
debug.set_next(info)  # debug tries first, then falls through to the existing chain

Because each handler only knows about "am I responsible, and if not, who's next," adding DebugHandler requires writing exactly one new class and wiring it into the chain — InfoHandler, WarningHandler, and ErrorHandler are completely untouched. This is the direct, structural payoff over the single dispatcher function: the original version needed a new elif branch in one growing function; this version needs one new, independent class.

How this differs from Strategy and Command, since all three involve interchangeable objects

Strategy picks one algorithm and commits to it for a call. Command wraps one request as an object to execute (and possibly undo) later. Chain of Responsibility is structurally different from both: it's not about choosing one thing upfront, or wrapping a request — it's a sequence of candidates, each independently deciding whether it's the right one to act, with the request potentially trying several handlers before (or without ever) finding one that actually handles it. The request doesn't know in advance which handler, if any, will actually process it.

The concrete signal a Chain of Responsibility belongs somewhere

The tell: several different handlers might apply to a request, the actual choice of which one should depends on inspecting the request itself, and hardcoding that dispatch logic into one place would force it to know about every handler that exists. Middleware pipelines (this platform's Node.js domain covers Express middleware, which is genuinely this same pattern — each middleware decides whether to act, then calls next()), event bubbling, and validation pipelines (try each validator in turn) are real, common real-world instances of this exact shape.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What problem does a single dispatcher function with an if/elif chain over every possible handler create?

2. How does each handler in a Chain of Responsibility decide what to do with a request?

3. What's a real, common example of the Chain of Responsibility pattern covered elsewhere on this platform?