Chain of Responsibility Pattern: Leave Request Approval Chain
The Chain of Responsibility Pattern allows a request to travel through a chain of handlers, each with a chance to deal with it. Instead of one part of your code handling every request, the work can be passed from one handler to the next until something is done.
This is useful when you have many rules or processes that could apply. You can add, remove, or reorder handlers without changing the code that sends the request. It helps keep your code open for change and closed for bugs.
Real-Life Analogy: Leave Request Approval Chain
Imagine you're an employee requesting leave. First, you ask your team leader. If they approve, great. If not, it goes up to the department manager. Still no? It reaches HR or upper management.
The request goes through each level until someone handles it — or it's rejected. That's how the Chain of Responsibility works in code.

Benefits of Chain of Responsibility
This pattern keeps your code clean by letting each class focus on just one type of responsibility. It also lets you add or remove handlers without changing the rest of the system.
It promotes flexibility and separation of concerns.
- Reduces coupling between sender and receiver
- Makes it easy to add or reorder handlers
- Encapsulates each handler's responsibility
What to Implement
To implement this pattern, you need:
A base handler class with a method to process the request and a reference to the next handler in the chain.
- Handler Interface: Declares a method like Handle()
- Concrete Handlers: Decide whether to process or pass along
- Client: Sends the request to the first handler
How It Works in C#
// Request
public class LeaveRequest
{
public int Days { get; }
public LeaveRequest(int days) => Days = days;
}
// Handler Interface
public abstract class Approver
{
protected Approver Next;
public void SetNext(Approver next) => Next = next;
public abstract void HandleRequest(LeaveRequest request);
}
// Concrete Handlers
public class TeamLead : Approver
{
public override void HandleRequest(LeaveRequest request)
{
if (request.Days <= 2)
Console.WriteLine(""TeamLead approved leave"");
else
Next?.HandleRequest(request);
}
}
public class Manager : Approver
{
public override void HandleRequest(LeaveRequest request)
{
if (request.Days <= 5)
Console.WriteLine(""Manager approved leave"");
else
Next?.HandleRequest(request);
}
}
public class HR : Approver
{
public override void HandleRequest(LeaveRequest request)
{
Console.WriteLine(""HR approved leave"");
}
}
Usage:
var teamLead = new TeamLead();
var manager = new Manager();
var hr = new HR();
teamLead.SetNext(manager);
manager.SetNext(hr);
var request = new LeaveRequest(4);
teamLead.HandleRequest(request); // Manager approved leave
When Should You Use It?
Use this pattern when multiple objects could handle a request — but you don't want to hard-code the order or decision logic.
It works best when:
- Requests may be handled by different objects
- Handlers need to pass along requests they can't handle
- You want to keep responsibilities loosely coupled
When Not to Use It: Avoid it if you know exactly which object should handle the request — no need to pass it down a chain.
Where Is It Used in the Real World?
Chain of Responsibility is widely used in systems where a request may be processed by multiple handlers in sequence. It's common in user interface frameworks and middleware.
- UI toolkits (event handling and bubbling)
- Middleware pipelines (web frameworks like ASP.NET Core, Express.js)
- Support ticket routing (escalation chains)
- Logging frameworks (log filters/processors in sequence)
Final Thoughts
The Chain of Responsibility Pattern is perfect for systems where the handler isn't fixed. It gives you flexibility, avoids hard-coded logic, and lets responsibilities shift as needed.
Whether it's handling approvals, commands, or support tickets — this pattern lets your code flow through the right hands.