AlgoMaster Logo

Chain of Responsibility Design Pattern

Last Updated: December 31, 2025

Ashish

Ashish Pratap Singh

6 min read

This pattern is useful when:

  • 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.

1. The Problem: Handling HTTP Requests

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:

  1. Authentication: Is the user properly authenticated via a token or session?
  2. Authorization: Is the authenticated user allowed to perform this action?
  3. Rate Limiting: Has the user exceeded their allowed number of requests?
  4. Data Validation: Is the request payload well-formed and valid?

Only after all these checks pass should the request reach the actual business logic.

The Naive Approach

A typical first attempt might look like this: implement all logic inside a single class using a long chain of if-else statements.

Client Code Example

This works fine for a simple case, but there are several problems hiding beneath the surface.

Why This Approach Breaks Down

1. Violates the Open/Closed Principle

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.

2. Poor Separation of Concerns

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.

3. No Reusability

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.

4. Inflexible Configuration

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.

What We Really Need

We need a way to:

  • Break each processing step into its own isolated unit
  • Let each step decide whether to continue, pass, or stop the chain
  • Allow new handlers to be added, removed, or reordered without touching existing code
  • Keep our logic clean, testable, and extensible

This is exactly what the Chain of Responsibility Pattern provides.

2. Understanding 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 and stop the chain
  • Pass it to the next handler in the chain
  • Handle the request and then pass it along

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.

Structure

The pattern consists of three main components:

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., AuthHandlerRateLimitHandler)

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.

3. 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

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

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