Bird
0
0
LLDsystem_design~15 mins

Chain of Responsibility pattern in LLD - Deep Dive

Choose your learning style9 modes available
Overview - Chain of Responsibility pattern
What is it?
The Chain of Responsibility pattern is a way to pass a request along a chain of objects until one of them handles it. Each object in the chain decides if it can process the request or pass it to the next. This helps separate the sender of a request from its receivers. It makes the system flexible and easy to extend without changing existing code.
Why it matters
Without this pattern, the sender must know exactly who will handle the request, making the system rigid and hard to change. The Chain of Responsibility lets requests flow through multiple handlers dynamically, so new handlers can be added or removed without affecting others. This reduces tight coupling and improves maintainability in complex systems.
Where it fits
Before learning this, you should understand basic object-oriented programming concepts like classes and interfaces. After this, you can explore other behavioral design patterns like Command or Observer, which also help manage communication between objects.
Mental Model
Core Idea
A request travels through a chain of handlers until one can process it, keeping sender and receiver loosely connected.
Think of it like...
Imagine a customer asking for help at a store. If the first employee can't answer, they pass the question to the next employee, and so on, until someone helps.
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Handler 1   │────▶│ Handler 2   │────▶│ Handler 3   │
└─────────────┘     └─────────────┘     └─────────────┘
       ▲                  ▲                  ▲
       │                  │                  │
    Request            Pass along         Handle or
    arrives            if can't           pass further
Build-Up - 6 Steps
1
FoundationUnderstanding Request Handling Basics
🤔
Concept: Requests need a way to be processed by objects that can handle them.
In many systems, an action or request is sent to an object. Sometimes, the object can handle it directly. Other times, it cannot and must pass it on. Without a clear way to pass requests, the system becomes tightly connected and hard to change.
Result
You see that requests often need a flexible way to find the right handler.
Understanding that requests may need to be passed along helps see why a chain is useful.
2
FoundationDefining Handlers and Chains
🤔
Concept: Handlers are objects that can process requests or pass them to the next handler in a chain.
Each handler has a reference to the next handler. When a request comes, the handler checks if it can process it. If yes, it does; if no, it forwards the request to the next handler. This creates a linked chain of responsibility.
Result
Requests flow through handlers until one processes it or the chain ends.
Knowing handlers link to each other forms the backbone of the pattern.
3
IntermediateImplementing Dynamic Chains
🤔Before reading on: do you think the chain must be fixed at compile time or can it change at runtime? Commit to your answer.
Concept: Chains can be built or changed dynamically at runtime to add flexibility.
Instead of hardcoding the chain, handlers can be linked or unlinked during program execution. This allows adding new handlers or changing the order without changing existing code. For example, a logging system might add new loggers on the fly.
Result
The system becomes adaptable to changing requirements without code rewrites.
Understanding dynamic chains reveals how the pattern supports flexible and maintainable designs.
4
IntermediateHandling Unprocessed Requests
🤔Before reading on: do you think a request always gets handled or can it sometimes be ignored? Commit to your answer.
Concept: Not all requests must be handled; the chain can end without processing.
If no handler in the chain can process the request, it simply passes through the entire chain and ends unhandled. Systems can choose to log, ignore, or raise errors in such cases. This behavior must be designed carefully.
Result
Requests may be dropped if no handler matches, which can be acceptable or problematic depending on context.
Knowing that unhandled requests are possible helps design chains that fail gracefully.
5
AdvancedCombining Chains with Other Patterns
🤔Before reading on: do you think Chain of Responsibility can work alone or is it often combined with other patterns? Commit to your answer.
Concept: Chain of Responsibility often works with patterns like Command or Decorator to build complex systems.
For example, a command object can be passed through a chain of handlers for validation, logging, or execution. Decorators can wrap handlers to add behavior. Combining patterns increases modularity and reuse.
Result
Systems become more powerful and flexible by layering patterns.
Understanding pattern combinations unlocks advanced design possibilities.
6
ExpertAvoiding Performance and Debugging Pitfalls
🤔Before reading on: do you think long chains always improve design or can they cause problems? Commit to your answer.
Concept: Long or complex chains can cause performance issues and make debugging harder.
If chains are too long, requests take more time to process. Also, tracing which handler processed a request can be difficult. Experts use logging, metrics, and chain length limits to manage these issues. Sometimes, alternative designs like event-driven systems are better.
Result
Awareness of these pitfalls leads to better, maintainable systems.
Knowing the limits of the pattern prevents misuse and costly bugs.
Under the Hood
Internally, each handler holds a reference to the next handler. When a request arrives, the handler checks if it can process it based on request properties or type. If it can, it handles the request and may stop propagation. If not, it calls the next handler's method. This creates a linked list of handlers passing the request along until handled or chain ends.
Why designed this way?
The pattern was designed to reduce coupling between sender and receiver. Early systems had rigid request handling, making changes costly. By passing requests along a chain, the system gains flexibility and extensibility. Alternatives like direct calls or switch statements were less maintainable and scalable.
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Handler A   │────▶│ Handler B   │────▶│ Handler C   │
│  can handle?│     │  can handle?│     │  can handle?│
│  yes/no    │     │  yes/no    │     │  yes/no    │
└─────────────┘     └─────────────┘     └─────────────┘
       │                  │                  │
       │ handle if yes    │ handle if yes    │ handle if yes
       └──────────────────┴──────────────────┴─────────────▶ End
Myth Busters - 4 Common Misconceptions
Quick: Does the Chain of Responsibility always guarantee a request will be handled? Commit to yes or no.
Common Belief:The chain always handles every request because it passes until someone processes it.
Tap to reveal reality
Reality:A request may pass through the entire chain unhandled if no handler matches.
Why it matters:Assuming all requests are handled can cause silent failures or lost requests in production.
Quick: Is the Chain of Responsibility pattern only useful for error handling? Commit to yes or no.
Common Belief:This pattern is mainly for handling errors or exceptions in a chain.
Tap to reveal reality
Reality:It applies to any request processing where multiple handlers may process or pass requests, not just errors.
Why it matters:Limiting the pattern to error handling misses its broader use in flexible request processing.
Quick: Can the chain be changed during runtime? Commit to yes or no.
Common Belief:The chain is fixed at design time and cannot be changed dynamically.
Tap to reveal reality
Reality:Chains can be built or modified dynamically at runtime for flexibility.
Why it matters:Believing chains are static limits design options and adaptability.
Quick: Does the first handler in the chain always process the request? Commit to yes or no.
Common Belief:The first handler always processes the request or stops the chain.
Tap to reveal reality
Reality:The first handler may pass the request along if it cannot handle it.
Why it matters:Assuming the first handler always processes requests can cause incorrect assumptions about system behavior.
Expert Zone
1
Handlers can choose to process a request partially and still pass it along, enabling multiple handlers to act on the same request.
2
Chains can be circular if not carefully designed, causing infinite loops; experts use safeguards to prevent this.
3
Performance monitoring of chain length and handler execution time is critical in high-load systems to avoid bottlenecks.
When NOT to use
Avoid using Chain of Responsibility when requests must be handled by multiple handlers simultaneously or when order of handling is strict and complex. In such cases, consider the Observer pattern or event-driven architectures instead.
Production Patterns
In real systems, chains are used in middleware pipelines (e.g., HTTP request processing), logging frameworks, and validation systems. Handlers often implement interfaces for uniformity, and chains are configured via dependency injection or configuration files for flexibility.
Connections
Middleware in Web Servers
Chain of Responsibility is the core pattern behind middleware chains.
Understanding this pattern clarifies how web servers process requests through layers like authentication, logging, and routing.
Event Bubbling in GUIs
Both involve passing events or requests through a chain until handled.
Knowing event bubbling helps grasp how Chain of Responsibility manages request propagation and handling.
Customer Service Escalation
Real-world process where requests escalate through levels until resolved.
Seeing this human process helps understand the pattern’s purpose in distributing responsibility flexibly.
Common Pitfalls
#1Creating very long chains without limits.
Wrong approach:handler1.setNext(handler2); handler2.setNext(handler3); ... handler100.setNext(null);
Correct approach:Limit chain length and monitor performance; consider splitting chains or using other patterns.
Root cause:Belief that longer chains always improve flexibility without considering performance or complexity.
#2Assuming every request will be handled and not coding for unhandled cases.
Wrong approach:void handle(Request r) { if (!canHandle(r)) next.handle(r); } // no fallback or error
Correct approach:void handle(Request r) { if (!canHandle(r)) { if (next != null) next.handle(r); else logUnhandled(r); } }
Root cause:Misunderstanding that chains can end without handling requests.
#3Hardcoding chain order and handlers, making changes difficult.
Wrong approach:HandlerA next = new HandlerB(); HandlerB next = new HandlerC(); // fixed chain in code
Correct approach:Use configuration or dependency injection to build chains dynamically at runtime.
Root cause:Not realizing the pattern supports dynamic chain construction.
Key Takeaways
Chain of Responsibility decouples senders and receivers by passing requests along a chain of handlers.
Handlers decide to process or pass requests, enabling flexible and extensible request processing.
Chains can be built or changed dynamically, supporting adaptable system designs.
Not all requests are guaranteed to be handled; systems must plan for unhandled cases.
Long or complex chains can cause performance and debugging challenges, so use the pattern wisely.