
Start with the design patterns overview · Part 13 of 23 · Behavioral patterns
Pass a request through an ordered chain until a handler processes it or the chain ends. This article follows the example in my 23 Design Patterns repository, connecting the problem, participating classes, Python and C++20 implementations, and the trade-offs that decide whether to use it.
The Problem
A request must pass authentication and spending checks, and different entry points need different policies.
Naive Solution
bool accept(Request r) {
return r.authenticated && r.amount > 0 && r.amount <= 100;
}
Why It Becomes a Problem
One expression is fine initially; duplicating and editing it for several pipelines makes policy order and reuse difficult.
The Idea
Each Handler runs its own check and delegates only on success; the final successful handler accepts.
Reading the illustration
A support ticket moves from the front desk to a specialist and then a manager only when the current person cannot resolve it.
In the repository example, the same design idea addresses this software problem: A request must pass authentication and spending checks, and different entry points need different policies.
Structure
Diagram · Python source · C++20 source
Request --> Auth --> Limit
Participants
Handler owns its successor. Auth checks identity, Limit checks amount. The client chooses the chain order.
Canonical roles in this example:
Handler— A role that handles a request or passes it to its successor. Here:Handler.Concrete Handler— A Handler implementing one particular processing rule. Here:Auth, Limit.chain termination— The rule for stopping a chain and deciding what happens after the last handler. Here:Handler::handle.
Python Example
The complete Python source is shown first.
class Request:
def __init__(self, authenticated, amount_cents):
self.authenticated = authenticated
self.amount_cents = amount_cents
class Handler:
def __init__(self, next_handler=None):
self.next_handler = next_handler
def accepts(self, request):
raise NotImplementedError
def handle(self, request):
if not self.accepts(request):
return False
if self.next_handler is None:
return True
return self.next_handler.handle(request)
class Auth(Handler):
def accepts(self, request):
return request.authenticated
class Limit(Handler):
def accepts(self, request):
return 0 < request.amount_cents <= 10000
if __name__ == "__main__":
chain = Auth(Limit())
for authenticated, amount_cents in [(False, 2000), (True, 20000),
(True, 2000), (True, 0), (True, 10000)]:
request = Request(authenticated, amount_cents)
print("Accepted" if chain.handle(request) else "Rejected")
Python Output
Rejected
Rejected
Accepted
Rejected
Accepted
Code Walkthrough
Handler owns its successor. Auth checks identity, Limit checks amount. The client chooses the chain order.
Start at the final call in the Python example. Follow the middle role in the diagram and compare how the C++20 version handles the same responsibility.
C++20 Example
// Monetary amounts in this example are integer cents.
#include <initializer_list>
#include <iostream>
#include <memory>
#include <utility>
struct Request { bool authenticated; int amount_cents; };
class Handler {
std::unique_ptr<Handler> next_;
protected:
virtual bool accepts(const Request& request) const = 0;
public:
explicit Handler(std::unique_ptr<Handler> next = {}) : next_(std::move(next)) {}
virtual ~Handler() = default;
bool handle(const Request& request) const {
if (!accepts(request)) return false;
return next_ ? next_->handle(request) : true;
}
};
class Auth final : public Handler {
bool accepts(const Request& request) const override { return request.authenticated; }
public:
using Handler::Handler;
};
class Limit final : public Handler {
bool accepts(const Request& request) const override { return request.amount_cents > 0 && request.amount_cents <= 100; }
public:
using Handler::Handler;
};
int main() {
const Auth chain{std::make_unique<Limit>()};
for (const auto& request : {Request{false, 20}, Request{true, 200}, Request{true, 20}})
std::cout << (chain.handle(request) ? "Accepted" : "Rejected") << '\n';
}
C++20 Output
Rejected
Rejected
Accepted
When to Use
Use it when request handling order or membership must be composed independently.
Use cases
Validation pipelines and request middleware fit. This variant requires every handler to approve, rather than stopping at the first successful handler.
When NOT to Use
Avoid it for two fixed checks in one place; the initial expression is then clearer.
Advantages
Checks can be reused and reordered without a giant conditional.
Trade-offs
Order affects behavior. A chain needs an explicit end policy; this example accepts after all checks, while other chains may reject unhandled requests.
Related Patterns
Common Confusion
Decorator layers behavior around a component; this chain may terminate without reaching later handlers. Command represents the request as an object.
Terms to Remember
Chain of Responsibility— Pass a request along handlers that can stop or continue processing.Handler— A role that handles a request or passes it to its successor. Example:Handler.Concrete Handler— A Handler implementing one particular processing rule. Example:Auth, Limit.chain termination— The rule for stopping a chain and deciding what happens after the last handler. Example:Handler::handle.
Interview Vocabulary
delegation— An object asks a collaborator to perform part of its work.object composition— Connecting objects to form a larger behavior or structure.loose coupling— Parts know only the small contracts needed to cooperate, limiting change propagation.
Interview Question
What happens to an unauthenticated request if Limit is expensive and placed first?
Mini Challenge
Add a maintenance-mode handler and verify that rejected requests never reach later checks.
Compare the two versions
Both versions use a validation chain: each Handler may reject, or pass onward; reaching the end means success. Other chains stop at the first Handler that can fulfill a request. Python holds successor references; C++ owns them with unique_ptr. The two examples express the same pattern responsibility; compare their setup and output before changing an input.
Check yourself
- What does reaching the end of this chain mean, and when does a check stop it?
- When would the naive solution on this page be easier to maintain? Give a concrete example.
- Change one input in the Python example. Predict the output and explain which responsibility handles the change.
Run and explore the example
The Python code comes from python/main.py, with expected output. The C++20 code comes from behavioral/chain-of-responsibility/cpp/main.cpp. Follow the repository’s C++20 build instructions to compile it and compare the result with expected.txt. The output demonstrates this example’s behavior; it does not cover every input or the challenge above.
The source example and adapted explanation are © 2026 Sanfor2004, provided under the MIT license. The sketchbook cover is an illustration preserved from this site’s original pattern lessons.
Continue the series
- Overview: all 23 patterns, their categories, and how to choose
- Previous: Proxy (Structural Pattern)
- Next: Command (Behavioral Pattern)