
Chain of Responsibility Design Pattern
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.
It’s particularly useful in situations where:
A request must be handled by one of many possible handlers, and you don’t want the sender to be tightly coupled to any specific one.
You want to decouple request logic from the code that processes it.
You want to flexibly add, remove, or reorder handlers without changing the client code.
When dealing with conditional request handling, developers often resort to long chains of if-else
or switch
statements to determine how a request should be processed. For example, a logging system might write to the console, file, or remote server depending on configuration, or an HTTP request might need to go through validation, authentication, and rate-limiting steps.
But as the number of conditions grows, this approach becomes hard to scale, violates the Open/Closed Principle, and turns your logic into a tightly coupled, brittle monolith.
The Chain of Responsibility Pattern solves this by turning individual processing steps into standalone classes, each responsible for one specific concern. These handlers are linked together to form a chain, and the request flows through the chain until it is handled (or dropped).
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.
The Problem: Handling HTTP Requests with Multiple Processing Steps
Imagine you’re building a backend server that processes incoming HTTP requests for a web application or RESTful API.
Here’s what a request might look like:
Each incoming request must go through a sequence of processing steps before it reaches the core business logic.
Common Pre-processing Steps
Authentication – Is the user properly authenticated (e.g., via token or session)?
Authorization – Is the authenticated user allowed to perform this action?
Rate Limiting – Has the user exceeded their allowed number of requests?
Data Validation – Is the request payload well-formed and valid?
Only after all these checks pass should the request reach the actual business logic (e.g., creating a resource, returning data, or updating a record).
A typical first attempt might look like this: implement all logic inside a single class using a long chain of if-else
or method calls.
Here’s a simplified version:
Client Code Example
Why This Approach Breaks Down
While this approach may work for a small number of checks, it quickly becomes problematic:
1. Hard to Extend or Modify
If you want to change the order of checks or add a new one (e.g., logging, metrics, caching), you must modify the existing handler — violating the Open/Closed Principle.
2. Poor Separation of Concerns
All validation and control logic is tightly coupled inside a single method. This violates the Single Responsibility Principle and makes the code harder to test and maintain.
3. No Reusability
You can’t easily reuse individual checks (e.g., rate limiting or authentication) in other parts of the system.
4. Inflexible Configuration
Want to skip authorization for public APIs? Want to make validation optional in dev mode? You’ll have to write more if
conditions and bloat the handler.
What We Really Need
We need a way to:
Break each step into its own unit of responsibility
Let each step decide whether to handle, pass, or short-circuit the request
Allow new handlers to be added, removed, or reordered without touching the existing code
Keep our logic clean, testable, and extensible
This is where the Chain of Responsibility Pattern comes in.
The Chain of Responsibility Pattern
The Chain of Responsibility Pattern allows a request to be passed along a chain of handlers. Each handler in the chain can either:
Handle the request, or
Pass it to the next handler in the chain
This pattern decouples the sender of the request from the receiver(s), giving you the flexibility to compose chains dynamically, reuse logic, and avoid long, rigid conditional blocks.
Class Diagram
1. Handler Interface (Abstract Base Class / Interface)
Declares a method like
handle(request)
for processing the request.Holds a reference to the next handler in the chain via
setNext(handler)
.Defines the contract for passing the request down the chain.
2. ConcreteHandlers (e.g., AuthHandler
, RateLimitHandler
)
Implement the
Handler
interface.Each handler decides if it will:
Handle the request (e.g., reject, log, transform), or
Pass the request along to the next handler in the chain.
3. Client
Builds and connects the chain of handlers using
setNext()
.Sends the request to the first handler in the chain.
Is unaware of which handler will ultimately process the request.
Implementing Chain of Responsibility
Let’s refactor our monolithic RequestHandler
into a clean, extensible chain of modular handlers using the Chain of Responsibility Pattern.
1. Define the Common Handler Interface
Every handler will implement this interface. Each handler should:
Perform its specific check
Decide whether to stop the chain or pass the request to the next handler
2. Create the Abstract Base Handler (Optional but Recommended)
To avoid duplicating the setNext()
and forwarding logic in every handler, we define an abstract base class with reusable functionality.
Now every concrete handler can focus solely on its logic and delegate to
forward(request)
when needed.
3. Create Concrete Handlers
Each handler implements one responsibility. They extend BaseHandler
, implement handle(Request)
, and determine whether to continue the chain or short-circuit it.
🔐 Authentication Handler
🔒 Authorization Handler
⏱️ Rate Limiting Handler
📦 Data Validation Handler
✅ Final Business Logic Handler
This is the last handler in the chain — it assumes the request has passed all previous checks.
4. Assemble the Chain in Client Code
Now that our handlers are modular, we can connect them in any order depending on the requirements.
Output
AuthHandler: ✅ Authenticated.
AuthorizationHandler: ✅ Authorized.
RateLimitHandler: ✅ Within rate limit.
ValidationHandler: ✅ Payload valid.
BusinessLogicHandler: 🚀 Processing request...
--- Trying an invalid request ---
AuthHandler: ❌ User not authenticated.
What We Achieved
Modularity: Each handler is isolated and easy to test
Loose Coupling: Handlers don’t need to know who comes next
Extensibility: Easily insert, remove, or reorder handlers
Clean Client Code: Only responsible for building the chain and sending the request
Open/Closed Compliant: You can add new functionality (e.g., LoggingHandler) without touching existing code
Additional Readings: