Skip to content
FollowtheDamnCode

Chain of Responsibility

Pass a request along a line of handlers until one deals with it — the sender never needs to know which one will.

Big idea

Chain of Responsibility lines up handlers that each get a chance at a request. Each one either handles it or passes it to the next. The sender talks only to the first link, so you can add, remove, or reorder handlers without the sender knowing.

The problem it solves

Expense claims need approval: a team lead can approve up to 500, a director up to 5,000, the CFO up to 50,000, and anything bigger goes to the board. Written as one function, that's an if amount <= 500 … elif … ladder that also has to know who's on holiday, which departments skip the director, and so on. Every policy change edits the same function; testing one rule means setting up all of them.

Think of it like

A customer support line. Your call reaches the first-line agent; if they can't fix it, they escalate to a specialist; if the specialist can't, it goes to engineering. You dialled one number. You don't know — or care — who finally solved it, and the company can add a new tier without changing its phone number.

Structure

Each handler holds a link to the next
A 3,200 claim walks the chain

In code

Approval limits as a chain
class Approver:
    def __init__(self, name, limit):
        self.name, self.limit, self.next = name, limit, None

    def set_next(self, approver):
        self.next = approver
        return approver                       # lets you chain the set_next calls

    def handle(self, amount, purpose):
        if amount <= self.limit:
            return f"{amount:>6} for {purpose}: approved by {self.name}"
        if self.next is None:
            return f"{amount:>6} for {purpose}: rejected, nobody can approve it"
        return self.next.handle(amount, purpose)


class OnHoliday(Approver):                    # a handler that always passes on
    def handle(self, amount, purpose):
        return self.next.handle(amount, purpose)


lead = Approver("team lead", 500)
lead.set_next(Approver("director", 5_000)).set_next(Approver("CFO", 50_000))

for amount, purpose in [(120, "taxi"), (3_200, "conference"), (40_000, "servers"), (90_000, "office")]:
    print(lead.handle(amount, purpose))

# the director is away: splice a pass-through in, the sender doesn't change
lead2 = Approver("team lead", 500)
lead2.set_next(OnHoliday("director", 0)).set_next(Approver("CFO", 50_000))
print(lead2.handle(3_200, "conference"))
output
   120 for taxi: approved by team lead
  3200 for conference: approved by director
 40000 for servers: approved by CFO
 90000 for office: rejected, nobody can approve it
  3200 for conference: approved by CFO

Run and edit it in the app.

Watch it

Variations

  • First handler wins (above) — the request stops once handled.
  • Every handler runs — a pipeline where each may act and still pass on; web middleware works this way (auth → rate limit → logging → handler), and any handler may short-circuit with an error.
  • Chain from a tree — a UI click bubbles from a button to its panel to the window until something handles it; the "next" link is the parent in a Composite.

What does the sender of a request know in Chain of Responsibility?

How to implement it

  1. Define a handler interface with handle(request), and a field for the next handler.
  2. Put "pass it on" logic in a base class so concrete handlers only decide can I handle this?
  3. Build the chain at the edges — configuration or a factory — not inside handlers.
  4. Decide what happens at the end of the chain: a default handler, an error, or a rejection.

When to use it — and when not to

Use it when several objects might handle a request and the right one depends on runtime conditions, when the set or order of handlers should be configurable, or for request pipelines (middleware, event bubbling, validation steps). Avoid it when exactly one handler is always responsible — a direct call is clearer — or when you need a guarantee that every request is handled and can't afford an unhandled fall-through.

Pros and cons

ProsCons
Sender decoupled from receiversA request may fall off the end unhandled
Handlers added or reordered freely (Open/Closed)Harder to see which handler acted
Each rule is its own small classLong chains cost time on every request

How it relates

  • Decorator has the same wrapping shape, but decorators always forward; chain links may stop the request.
  • Command objects are often what travels along the chain.
  • Composite parent links frequently form the chain.
  • Mediator and Observer are the other ways to connect senders and receivers: one central hub, and broadcast to all subscribers.

In web middleware, the auth step rejects a request with a 401. What happens to the steps after it?

Key terms

  • Chain of Responsibility — Handlers linked in sequence, each handling a request or passing it to the next.
  • Handler — An object that can process a request or forward it.
  • Short-circuit — A handler ending the chain early, for example by rejecting a request.
  • Middleware — A chain of handlers around a web request, each able to act or stop it.