Last Updated: February 24, 2026
The Chain of Responsibility Design Pattern is a behavioral pattern that lets you pass requests along a chain of handlers, allowing each handler to decide whether to process the request or pass it to the next handler in the chain.
This pattern is useful when:
Let’s walk through a real-world example to see how we can apply the Chain of Responsibility Pattern to build a clean, modular, and extensible pipeline for request processing.
Let us say you are building a backend server that processes incoming HTTP requests for a web application. Each request carries some information about the user, their role, how many requests they have made, and some payload data.
Before the request reaches the actual business logic, it must pass through several processing steps:
Only after all these checks pass should the request reach the actual business logic.
A typical first attempt might look like this: implement all logic inside a single class using a long chain of if-else statements.
This works fine for a simple case, but there are several problems hiding beneath the surface.
Every time you need to add a new check, say logging, caching, or metrics collection, you must modify the existing RequestProcessor class. The class is open for modification when it should be closed for changes and open for extension.
All validation and control logic is tightly coupled inside a single method. This violates the Single Responsibility Principle. The class is doing too many things: authentication, authorization, rate limiting, validation, and business logic coordination.
What if another service needs the same authentication logic? You would have to copy the code or create awkward shared methods. Neither option is clean.
What if you want to skip authorization for public APIs? Or make validation optional in development mode? You would need more if statements, and the code would become even more tangled.
We need a way to:
This is exactly what the Chain of Responsibility Pattern provides.
The Chain of Responsibility Pattern allows a request to be passed along a chain of handlers. Each handler in the chain can either:
This pattern decouples the sender of the request from the receivers, giving you the flexibility to compose chains dynamically, reuse logic, and avoid rigid conditional blocks.
Think about calling customer support. You explain your issue to the first-level agent. If they can solve it, great. If not, they escalate to a second-level specialist. That specialist might handle it or escalate further to a manager, who might escalate to engineering.
You, the caller, do not decide who handles your issue. You just start at the beginning, and the request moves through the chain until someone resolves it.
The Chain of Responsibility pattern works the same way: the client sends a request to the first handler, and it flows through the chain until one handler processes it or the chain ends.
The pattern consists of three main components:
Declares the common interface for all handlers in the chain. It defines how to set the next handler and how to process a request.
The interface defines the chaining contract. Every handler in the chain can be treated uniformly, whether it is an authentication check, a rate limiter, or the final business logic handler.
AuthHandler, RateLimitHandler)Each concrete handler implements one processing step. It decides whether to handle the request, reject it, or pass it to the next handler.
Builds the chain by linking handlers together and sends the initial request to the first handler. The client is unaware of which handler ultimately processes the request
The Chain of Responsibility workflow follows a clear sequence:
Step 1: The client creates concrete handler objects (e.g., AuthHandler, RateLimitHandler, ValidationHandler).
Step 2: The client links them together using setNext(), forming the chain in the desired processing order.
Step 3: The client sends a request to the first handler in the chain.
Step 4: The first handler processes the request. If the request passes its check, the handler calls forward() to pass the request to the next handler. If the check fails, the handler stops the chain.
Step 5: This continues until a handler stops the chain or the request reaches the end.
Let us refactor our monolithic RequestProcessor into a clean, extensible chain of modular handlers using the Chain of Responsibility Pattern.
Every handler will implement this interface. Each handler should perform its specific check and decide whether to stop the chain or pass the request to the next handler.
To avoid duplicating the setNext() and forwarding logic in every handler, we define an abstract base class with reusable functionality. This way, concrete handlers only need to implement their specific check.
Now every concrete handler can focus solely on its logic and delegate to forward(request) when needed.
Each handler implements one responsibility. They extend BaseHandler, implement handle(Request), and determine whether to continue the chain or short-circuit it.
This is the last handler in the chain. It assumes the request has passed all previous checks.
Now that our handlers are modular, we can connect them in any order depending on the requirements.
Notice how clean this is. No conditional logic orchestrating the processing steps. Each handler is isolated and focused on one concern. Adding a new handler (say, LoggingHandler or CorsHandler) only requires creating a new class and plugging it into the chain. No changes to existing handlers or the client code structure.
This is the power of the Chain of Responsibility pattern: it turns a rigid, monolithic processing method into a flexible, composable pipeline.
Let us work through a second example to reinforce the pattern. This time, we are building an ATM cash dispenser where a withdrawal request passes through a chain of denomination handlers. Each handler dispenses as many notes as it can of its denomination, then forwards the remaining amount to the next handler in the chain.
The key difference from our HTTP middleware example: here, every handler modifies the request before forwarding it. The HTTP middleware either passes or rejects. The ATM handlers always process and always forward, each one reducing the remaining amount. This is the "process and forward" variant of the pattern, where every handler does its part and hands off the rest.
The first withdrawal ($380) divides cleanly across the denominations, so the remaining amount hits zero. The second withdrawal ($275) leaves $5 unhandled because none of our handlers deal in denominations smaller than $10. In a real ATM, the system would check this remainder and warn the user before dispensing.
This is a great example of how Chain of Responsibility is not just about filtering. It works equally well when handlers need to collaborate on a shared task, each contributing its piece.