You call customer support with a problem. Level 1 tries to help. If they can't solve it, they escalate to Level 2. If Level 2 is stuck, it goes to Level 3. The request travels down a line until someone handles it — and you, the caller, never needed to know which level would end up solving it.
That's the Chain of Responsibility pattern: pass a request along a chain of handlers until one of them handles it.
The problem it solves
Imagine approving an expense, where the approver depends on the amount:
public void Approve(decimal amount)
{
if (amount <= 1000) Console.WriteLine("Team lead approves");
else if (amount <= 10000) Console.WriteLine("Manager approves");
else if (amount <= 100000) Console.WriteLine("Director approves");
else Console.WriteLine("CFO approves");
}
It works, but every new approval level means editing this one method, and the sender is hard-wired to know the entire hierarchy. Chain of Responsibility turns each level into its own handler and links them together.
The Chain way
Each handler knows one thing to do and who to pass to if it can't:
public abstract class Approver
{
protected Approver? _next;
public Approver SetNext(Approver next)
{
_next = next;
return next; // return next so we can chain the setup fluently
}
public abstract void Handle(decimal amount);
}
Each concrete handler either handles the request or forwards it:
public class TeamLead : Approver
{
public override void Handle(decimal amount)
{
if (amount <= 1000) Console.WriteLine("Team lead approves");
else _next?.Handle(amount); // can't handle — pass it on
}
}
public class Manager : Approver
{
public override void Handle(decimal amount)
{
if (amount <= 10000) Console.WriteLine("Manager approves");
else _next?.Handle(amount);
}
}
public class Director : Approver
{
public override void Handle(decimal amount)
{
if (amount <= 100000) Console.WriteLine("Director approves");
else _next?.Handle(amount);
}
}
Link the chain, then send requests to the front of it:
var teamLead = new TeamLead();
var manager = new Manager();
var director = new Director();
teamLead.SetNext(manager).SetNext(director); // build the chain
teamLead.Handle(500); // Team lead approves
teamLead.Handle(5000); // Manager approves
teamLead.Handle(50000); // Director approves
The caller always talks to the front of the chain and never worries about who ultimately handles the request. Add a CFO level? Write one handler and link it on the end — nothing else changes.
The key idea
Each handler makes one small decision: can I deal with this? If yes, handle it and stop. If no, pass it to the next one. No single place holds the whole branching logic — it's distributed across small, independent handlers you can reorder, insert, or remove freely.
You can even design chains where every handler runs (each doing its bit and always passing along), rather than stopping at the first match — useful for pipelines where several steps should all get a turn.
Where you've seen it
-
ASP.NET Core middleware — the single clearest example. Each middleware inspects the request, does its bit (auth, logging, compression), and calls
next()to pass control down the pipeline. - Logging frameworks — a message passes through handlers filtered by level (Debug, Info, Warning, Error).
- Event bubbling in UIs — a click travels up through parent elements until something handles it.
- Validation pipelines — each rule checks the input and passes it on.
Chain of Responsibility vs Decorator
Both build a chain of wrapped objects, so they look related. The difference is intent. A Decorator always calls the next one and its whole point is to add behavior around the result. A Chain of Responsibility handler may stop the chain — the point is to find the one handler that should deal with the request. Decorator enhances every layer; Chain looks for a place to stop.
One line to remember
Chain of Responsibility = support escalation. A request travels down a line of handlers until one deals with it — and the sender never needs to know which one.
Part 15 — the finale — of a series on must-know design patterns in C#, explained the simple way. Across the series we covered all fifteen: Singleton, Factory Method, Builder, Adapter, Decorator, Facade, Proxy, Composite, Observer, Strategy, Command, Iterator, State, Template Method, and Chain of Responsibility. If you followed along from part one, you now have a working mental model for every pattern on the list — each tied to something from everyday life. Thanks for reading.
Top comments (0)