Low Level Design
Chain of Responsibility — Pass Requests Through a Handler Chain
How the Chain of Responsibility pattern decouples request senders from receivers, letting multiple objects get a chance to handle a request without the sender knowing which one will.
What is the Chain of Responsibility Pattern?#
The Chain of Responsibility is a behavioral pattern that lets you pass requests along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler in the chain.
The sender is completely decoupled from the receiver — it just fires the request and the chain figures out who handles it.
You reach for Chain of Responsibility when:
- Multiple objects may handle a request, but you don't know which one in advance
- You want to issue requests to one of several objects without specifying the receiver explicitly
- The set of handlers should be composable and dynamic
- You want to process requests in a specific priority order
Real-World Analogy#
Think of a customer service call center. When you call with a problem:
- 🤖 Automated system handles simple queries (e.g., balance check)
- 👤 Junior rep handles basic issues (e.g., billing questions)
- 👩💼 Senior rep handles complex problems (e.g., service disputes)
- 🏢 Supervisor handles escalated complaints
Each level handles what it can and passes harder issues up. You (the caller) don't decide who handles your request — the chain does.
Class Diagram#
Violation Code — The Problem#
class SupportService {
public void handleRequest(String issue, int priority) {
if (priority == 1) {
System.out.println("Level 1 handling: " + issue);
} else if (priority == 2) {
System.out.println("Level 2 handling: " + issue);
} else if (priority == 3) {
System.out.println("Level 3 handling: " + issue);
} else {
System.out.println("Manager handling: " + issue);
}
// Adding priority 5 means touching this method ❌
}
}
Issues:
- No abstraction — no common Handler interface; all logic packed in one method
- Tight coupling — all support levels crammed into one class
- No delegation — each level isn't responsible for its own logic
- Violates OCP — adding a new support level means modifying existing code
- Untestable in isolation — can't test individual levels independently
Enhanced Code — Chain of Responsibility#
// Abstract handler — defines the chain link
public abstract class SupportHandler {
private SupportHandler nextHandler;
public SupportHandler setNext(SupportHandler next) {
this.nextHandler = next;
return next; // enables chaining: l1.setNext(l2).setNext(l3)
}
public void handleRequest(String issue, int priority) {
if (nextHandler != null) {
nextHandler.handleRequest(issue, priority);
} else {
System.out.println("No handler found for priority " + priority);
}
}
}
// Concrete handlers
public class Level1Support extends SupportHandler {
@Override
public void handleRequest(String issue, int priority) {
if (priority == 1) {
System.out.println("Level 1: Handling '" + issue + "'");
} else {
System.out.println("Level 1: Escalating...");
super.handleRequest(issue, priority);
}
}
}
public class Level2Support extends SupportHandler {
@Override
public void handleRequest(String issue, int priority) {
if (priority == 2) {
System.out.println("Level 2: Handling '" + issue + "'");
} else {
System.out.println("Level 2: Escalating...");
super.handleRequest(issue, priority);
}
}
}
public class Level3Support extends SupportHandler {
@Override
public void handleRequest(String issue, int priority) {
if (priority == 3) {
System.out.println("Level 3: Handling '" + issue + "'");
} else {
System.out.println("Level 3: Escalating...");
super.handleRequest(issue, priority);
}
}
}
public class ManagerSupport extends SupportHandler {
@Override
public void handleRequest(String issue, int priority) {
System.out.println("Manager: Taking ownership of '" + issue + "'");
}
}
// Build the chain
public class Main {
public static void main(String[] args) {
SupportHandler l1 = new Level1Support();
SupportHandler l2 = new Level2Support();
SupportHandler l3 = new Level3Support();
SupportHandler manager = new ManagerSupport();
l1.setNext(l2).setNext(l3).setNext(manager); // chain it
l1.handleRequest("Password reset", 1); // Level 1 handles
l1.handleRequest("Billing dispute", 2); // Level 2 handles
l1.handleRequest("Account hacked", 4); // Manager handles
}
}
Use a Builder or Factory to construct the chain — this keeps the chain wiring out of business logic.
Common LLD Problems Using Chain of Responsibility#
1. Logging System#
- Handlers: DebugLogger, InfoLogger, WarningLogger, ErrorLogger
- Context: Messages pass through logger levels based on severity.
2. HTTP Middleware Pipeline (Web Frameworks)#
- Handlers: AuthMiddleware, RateLimiter, RequestLogger, RequestValidator
- Context: Each middleware processes the request and passes it on (or rejects it).
3. Expense Approval Workflow#
- Handlers: ManagerApproval, DirectorApproval, CFOApproval
- Context: Expense requests escalate until approved at the appropriate authority level.
4. Event Handling System#
- Handlers: MouseEventHandler, KeyboardEventHandler, TouchEventHandler
- Context: Events bubble up through the chain until a handler processes them.
5. Access Control / Authorization#
- Handlers: RoleCheckHandler, PermissionHandler, OwnershipHandler
- Context: Multiple access rules are checked in sequence before granting access.
6. Form Validation Pipeline#
- Handlers: EmptyFieldValidator, EmailFormatValidator, PasswordStrengthValidator
- Context: Each validator passes or blocks the form submission.
7. Spam Filter / Email Classifier#
- Handlers: KeywordFilter, SenderReputationFilter, DomainFilter
- Context: Emails pass through multiple filters to determine legitimacy.
8. Technical Support Escalation#
- Handlers: Tier1Support, Tier2Support, Tier3Support, Engineering
- Context: Issues are passed up the chain until they land with someone who can resolve them.
Key Design Tips#
- Use a Builder or Factory to wire the chain — keeps chain construction separate from handler logic
- Decide on fallback — does an unhandled request fail silently or throw an exception?
- Each handler should do one thing — SRP applies per link in the chain
- Chain order matters — handlers are tried in the order they are linked
| References | Links |
|---|---|
| Article Reference | Refactoring Guru — Chain of Responsibility |