Skip to content

Chain of Responsibility

Chain of Responsibility lets you pass a request through a sequence of handlers. Each handler either deals with the request or forwards it to the next one, so the sender never needs to know which handler will end up doing the work.

Imagine validating an incoming request before your application acts on it. You might check that required fields are present, that the payload is not too large, that the caller is authenticated, and that they have permission. Cramming all of that into one giant function produces a tangle of nested conditionals, and reordering or removing a check means surgery on that one block.

Chain of Responsibility turns each check into its own small handler with a single responsibility. The handlers are linked in order; a request enters the front of the chain and travels along it until a handler rejects it or it falls off the end having passed every check. Adding, removing, or reordering checks becomes a matter of relinking handlers, not rewriting logic, and the code that submits a request stays blissfully unaware of how many handlers there are.

classDiagram
  class Handler {
    <<interface>>
    +setNext(h Handler) Handler
    +handle(request) Result
  }
  class BaseHandler {
    -next Handler
    +setNext(h Handler) Handler
    +handle(request) Result
  }
  class ConcreteHandlerA {
    +handle(request) Result
  }
  class ConcreteHandlerB {
    +handle(request) Result
  }
  Handler <|.. BaseHandler
  BaseHandler <|-- ConcreteHandlerA
  BaseHandler <|-- ConcreteHandlerB
  BaseHandler --> Handler : next
Each handler holds a reference to the next and may forward the request
  • Handler — the interface every link shares: a way to set the next handler and a method that processes a request.
  • BaseHandler — optional shared base that stores the next link and forwards by default, so concrete handlers only override what they care about.
  • ConcreteHandler — a specific check or step. It either resolves the request or delegates to the next handler in line.
  • Client — builds the chain and submits requests to its head, never to a specific handler.

A small validation chain for a sign-up request. Each handler inspects the request and either reports a problem or passes it down the line. An empty result means every handler approved it.

interface SignUp {
email: string;
password: string;
}
abstract class Validator {
private next: Validator | null = null;
setNext(next: Validator): Validator {
this.next = next;
return next;
}
validate(req: SignUp): string | null {
const error = this.check(req);
if (error !== null) return error;
return this.next ? this.next.validate(req) : null;
}
protected abstract check(req: SignUp): string | null;
}
class EmailValidator extends Validator {
protected check(req: SignUp): string | null {
return req.email.includes('@') ? null : 'email is invalid';
}
}
class PasswordValidator extends Validator {
protected check(req: SignUp): string | null {
return req.password.length >= 8 ? null : 'password too short';
}
}
const chain = new EmailValidator();
chain.setNext(new PasswordValidator());
console.log(chain.validate({ email: '[email protected]', password: 'secret12' })); // null
console.log(chain.validate({ email: 'nope', password: 'secret12' })); // email is invalid
  • Pro: decouples the sender of a request from whichever handler ends up processing it.
  • Pro: each handler has one job, and you reorder or extend the pipeline by relinking, not rewriting.
  • Con: a request can fall off the end of the chain unhandled; you must decide whether that is allowed.
  • Con: debugging is harder because behaviour is spread across many small handlers and depends on their order.
  • Command often travels as the request object that a chain processes.
  • Composite and Chain of Responsibility both build linked structures, but a composite is a tree you operate on, while a chain is a line a request passes along.
What does each handler in a Chain of Responsibility decide?
Why is the sender decoupled from the receiver in this pattern?
What is a risk of using a chain?