Skip to content

Chain of Responsibility Pattern — Passing Requests Along a Chain

DodaTech Updated 2026-06-24 4 min read

What You'll Learn

You will learn how the Chain of Responsibility pattern builds a pipeline of handlers, each deciding whether to Process a request or pass it to the next handler. You will see how this decouples request senders from receivers.

Why It Matters

Hardcoding if-else chains for request handling makes systems rigid and hard to extend. Every new handler requires modifying existing code. Chain of Responsibility lets you add, remove, or reorder handlers without touching the rest of the chain. Consider an HTTP request filter: without the chain, adding a new CORS check means inserting an if-block into existing filtering code. With the chain, you write a new handler class and insert it into the pipeline configuration.

Real-World Use

Web frameworks route HTTP requests through middleware layers — authentication, logging, Rate Limiting, CORS — each either handling the request or passing it along. DodaTech's CLI command pipeline uses Chain of Responsibility to validate flags, load config files, and execute subcommands in sequence. Adding a new validation step means writing one handler and inserting it into the chain — no existing code changes.

The Pattern

Handler declares an interface for processing requests and optionally holding a reference to the next handler. ConcreteHandler decides whether to handle or forward.

from abc import ABC, abstractmethod

class LogHandler(ABC):
    def __init__(self):
        self._next = None

    def set_next(self, handler: "LogHandler") -> "LogHandler":
        self._next = handler
        return handler

    @abstractmethod
    def handle(self, level: str, message: str) -> str:
        pass

class DebugHandler(LogHandler):
    def handle(self, level: str, message: str) -> str:
        if level == "DEBUG":
            return f"DEBUG: {message}"
        if self._next:
            return self._next.handle(level, message)
        return ""

class InfoHandler(LogHandler):
    def handle(self, level: str, message: str) -> str:
        if level == "INFO":
            return f"INFO: {message}"
        if self._next:
            return self._next.handle(level, message)
        return ""

class ErrorHandler(LogHandler):
    def handle(self, level: str, message: str) -> str:
        if level == "ERROR":
            return f"ERROR: {message}"
        if self._next:
            return self._next.handle(level, message)
        return ""
chain = DebugHandler()
chain.set_next(InfoHandler()).set_next(ErrorHandler())

for msg_level, msg in [("DEBUG", "starting up"), ("INFO", "config loaded"), ("ERROR", "disk full"), ("WARN", "low memory")]:
    result = chain.handle(msg_level, msg)
    if result:
        print(result)
    else:
        print(f"Unhandled: {msg_level} {msg}")
DEBUG: starting up
INFO: config loaded
ERROR: disk full
Unhandled: WARN low memory

Structure

classDiagram
    class Handler {
        <>
        +setNext(handler): Handler
        +handle(request)
    }
    class ConcreteHandler1 {
        +handle(request)
    }
    class ConcreteHandler2 {
        +handle(request)
    }
    class ConcreteHandler3 {
        +handle(request)
    }
    ConcreteHandler1 ..|> Handler
    ConcreteHandler2 ..|> Handler
    ConcreteHandler3 ..|> Handler
    ConcreteHandler1 --> ConcreteHandler2 : next
    ConcreteHandler2 --> ConcreteHandler3 : next

Real-World Usage

  • ASP.NET Core Middleware — HTTP requests flow through a pipeline of middleware delegates that can handle early (auth) or pass through.
  • Java servlet filtersjavax.servlet.Filter chains implement logging, compression, and authentication.
  • JavaScript event bubbling — DOM events propagate from target element up to document root, with each handler able to stop propagation.
  • Unix pipelinesgrep | sort | uniq is a chain where each command processes and forwards data.
  • Command encapsulates requests; Chain of Responsibility routes them.
  • Composite has a similar tree-like structure but focuses on part-whole hierarchies.
  • Decorator wraps behaviour around an object; Chain passes behaviour along a sequence.
  • Observer broadcasts to many; Chain delivers to exactly one or until handled.

Pros and Cons

Pros Cons
Decouples sender and receiver No guarantee a request will be handled
Easy to add, remove, or reorder handlers Can be hard to debug the path a request takes
Follows the Single Responsibility Principle Handler chains can become long and slow
Runtime flexibility in processing order Request-specific context must be passed explicitly

The logging chain demonstrates the essential flow: each handler checks whether it can Process the request. If yes, it returns a result and the chain stops. If no, it forwards to the next handler. The set_next method returns the next handler to enable fluent chaining: DebugHandler().set_next(InfoHandler()).set_next(ErrorHandler()).

Practice Questions

  1. How would you implement a break mechanism that stops the chain after a handler produces a result?
  2. What are the testing strategies for an individual handler versus the entire chain?
  3. Implement a chain that validates user input through sanitisation, formatting, authorisation, and persistence steps.
  4. Compare Chain of Responsibility with a simple list of callbacks — what does the pattern provide that callbacks do not?

Challenge

Implement a chain where handlers can modify the request before passing it along (like middleware that adds headers to an HTTP request). Each handler should both Process and forward.

Real-World Task

Map your application's request validation or preprocessing pipeline to a Chain of Responsibility. Use DodaTech's pipeline debugger to trace how requests flow through each handler.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro