🔄 Behavioral

Chain of Responsibility Pattern

Passes a request along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain. This pattern decouples senders from receivers, giving multiple objects a chance to handle the request.

Problem

You have a sequence of checks or processing steps, and the order or composition of those steps may vary. Hardcoding the processing chain creates rigid, hard-to-maintain code. Adding or removing steps requires modifying the chain construction. Each handler is coupled to the next, making it hard to reuse handlers independently.

Solution

Define a handler interface with a method for handling requests and a method for setting the next handler. Each concrete handler either processes the request or forwards it to the next. The client can compose chains dynamically. Handlers are independent and can be reused in different chains.

Participants

RoleResponsibility
HandlerDeclares an interface for handling requests and optionally a link to the next handler
ConcreteHandlerHandles requests it is responsible for; forwards unhandled requests to the next handler
ClientInitiates the request to a handler in the chain

TypeScript Example

TypeScript
interface Middleware {
  setNext(middleware: Middleware): Middleware;
  handle(request: Request): Response | null;
}

interface Request {
  path: string;
  headers: Record<string, string>;
  body?: string;
}

interface Response {
  status: number;
  body: string;
}

abstract class BaseMiddleware implements Middleware {
  private next: Middleware | null = null;

  setNext(middleware: Middleware): Middleware {
    this.next = middleware;
    return middleware;
  }

  handle(request: Request): Response | null {
    if (this.next) return this.next.handle(request);
    return null;
  }
}

class AuthMiddleware extends BaseMiddleware {
  handle(request: Request): Response | null {
    if (!request.headers["authorization"]) {
      return { status: 401, body: "Unauthorized" };
    }
    console.log("Auth: passed");
    return super.handle(request);
  }
}

class RateLimitMiddleware extends BaseMiddleware {
  private requests = 0;
  handle(request: Request): Response | null {
    if (++this.requests > 100) {
      return { status: 429, body: "Too Many Requests" };
    }
    console.log(`Rate limit: ${this.requests}/100`);
    return super.handle(request);
  }
}

class RouteHandler extends BaseMiddleware {
  handle(request: Request): Response | null {
    return { status: 200, body: `Handled ${request.path}` };
  }
}

const auth = new AuthMiddleware();
const rateLimit = new RateLimitMiddleware();
const router = new RouteHandler();

auth.setNext(rateLimit).setNext(router);

const req: Request = {
  path: "/api/users",
  headers: { authorization: "Bearer token123" },
};
console.log(auth.handle(req));

Real-World Example

HTTP middleware pipelines in Express.js, Koa, and ASP.NET are chains of responsibility. Each middleware can short-circuit the chain (e.g., returning 401 for missing auth) or pass the request to the next middleware. DOM event bubbling in browsers propagates events up the element chain. Exception handling cascades through try/catch blocks. Approval workflows in enterprise systems pass requests through levels of authority.

When to Use

  • More than one object may handle a request, and the handler isn't known a priori
  • You want to issue a request to one of several objects without specifying the receiver explicitly
  • The set of handlers and their order should be configurable at runtime
  • You want to decouple request senders from receivers

When Not to Use

  • Every request must be handled and there's no fallback (guaranteeing handling is hard with chains)
  • The chain is too long, making debugging and request tracing difficult
  • Performance is critical and the overhead of traversing the chain is unacceptable

Understanding the Chain of Responsibility Pattern

The Chain of Responsibility pattern is one of the most important behavioral design patterns in object-oriented software design. Originally cataloged by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," this pattern continues to be a cornerstone of modern software architecture.

Passes a request along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain. This pattern decouples senders from receivers, giving multiple objects a chance to handle the request. The pattern involves the following key participants: Handler, ConcreteHandler, Client. Each participant has a well-defined role, and the interactions between them create a flexible, maintainable structure that can evolve with changing requirements.

In TypeScript and modern JavaScript development, the Chain of Responsibility pattern is particularly valuable because the language's type system and interface support allow you to express the pattern with compile-time safety. TypeScript generics can make Chain of Responsibility implementations even more flexible and reusable while maintaining strong type checking throughout the pattern's structure.

Knowing when to apply the Chain of Responsibility pattern is as important as knowing how. Common use cases include: More than one object may handle a request, and the handler isn't known a priori; You want to issue a request to one of several objects without specifying the receiver explicitly; The set of handlers and their order should be configurable at runtime. However, overusing design patterns can lead to unnecessarily complex code. Always evaluate whether the pattern genuinely solves your problem or if a simpler approach would suffice.

The Chain of Responsibility pattern is related to several other design patterns: Decorator, Command, Composite, Observer. Understanding these relationships helps you choose the right pattern for your specific situation and combine patterns effectively when building complex systems.

Related Patterns

More Behavioral Patterns

Explore All Design Patterns

Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.