Bird
Raised Fist0
LLDsystem_design~15 mins

Chain of Responsibility pattern in LLD - Deep Dive

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
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.

Practice

(1/5)
1. What is the main purpose of the Chain of Responsibility pattern in system design?
easy
A. To pass a request along a chain of handlers until one handles it
B. To create multiple copies of an object
C. To ensure only one instance of a class exists
D. To define a family of algorithms and make them interchangeable

Solution

  1. Step 1: Understand the pattern's behavior

    The Chain of Responsibility pattern allows a request to be passed along a chain of objects until one can handle it.
  2. Step 2: Compare options with the pattern's purpose

    The options describing singleton (one instance), prototype (object copies), and strategy (interchangeable algorithms) refer to other design patterns, not Chain of Responsibility.
  3. Final Answer:

    To pass a request along a chain of handlers until one handles it -> Option A
  4. Quick Check:

    Chain of Responsibility = pass request along chain [OK]
Hint: Remember: Chain passes request until handled [OK]
Common Mistakes:
  • Confusing with Singleton or Strategy patterns
  • Thinking it creates object copies
  • Assuming it handles all requests at once
2. Which of the following is the correct way to link handlers in a Chain of Responsibility pattern?
easy
A. Handlers are linked using a global static list
B. Handlers are independent and do not reference each other
C. Handlers communicate through a shared database
D. Each handler holds a reference to the next handler in the chain

Solution

  1. Step 1: Recall how handlers are connected

    In Chain of Responsibility, each handler has a reference to the next handler to pass the request along.
  2. Step 2: Evaluate other options

    Global static lists, shared databases, and independent handlers describe unrelated or incorrect linking methods that contradict the chain concept.
  3. Final Answer:

    Each handler holds a reference to the next handler in the chain -> Option D
  4. Quick Check:

    Handler links = next handler reference [OK]
Hint: Handlers link by referencing the next handler [OK]
Common Mistakes:
  • Using global lists instead of direct references
  • Assuming handlers are independent
  • Confusing with event broadcasting
3. Consider this simplified handler chain code snippet:
class Handler:
    def __init__(self, successor=None):
        self.successor = successor
    def handle(self, request):
        if self.can_handle(request):
            return f"Handled {request}"
        elif self.successor:
            return self.successor.handle(request)
        else:
            return "Not handled"
    def can_handle(self, request):
        return False

class ConcreteHandlerA(Handler):
    def can_handle(self, request):
        return request == 'A'

class ConcreteHandlerB(Handler):
    def can_handle(self, request):
        return request == 'B'

chain = ConcreteHandlerA(ConcreteHandlerB())
print(chain.handle('B'))

What is the output of this code?
medium
A. Handled B
B. Handled A
C. Not handled
D. Error

Solution

  1. Step 1: Trace the request through the chain

    The request 'B' is first checked by ConcreteHandlerA, which returns False for can_handle('B'). It passes the request to ConcreteHandlerB.
  2. Step 2: ConcreteHandlerB handles the request

    ConcreteHandlerB's can_handle('B') returns True, so it returns "Handled B".
  3. Final Answer:

    Handled B -> Option A
  4. Quick Check:

    Request 'B' handled by second handler [OK]
Hint: Request passes chain until a handler returns true [OK]
Common Mistakes:
  • Assuming first handler handles all requests
  • Confusing return values
  • Missing the chain passing logic
4. Given the following code snippet, identify the bug that breaks the Chain of Responsibility pattern:
class Handler:
    def __init__(self, successor=None):
        self.successor = successor
    def handle(self, request):
        if self.can_handle(request):
            return f"Handled {request}"
        else:
            return "Not handled"
    def can_handle(self, request):
        return False

class ConcreteHandler(Handler):
    def can_handle(self, request):
        return request == 'X'

chain = ConcreteHandler()
print(chain.handle('Y'))

What is the main issue here?
medium
A. The successor is not initialized properly
B. The can_handle method always returns True
C. The handler does not pass the request to the successor
D. The handle method has a syntax error

Solution

  1. Step 1: Analyze the handle method logic

    The handle method checks can_handle; if False, it returns "Not handled" immediately without passing to successor.
  2. Step 2: Identify missing chain passing

    It should call self.successor.handle(request) if successor exists, but this is missing.
  3. Final Answer:

    The handler does not pass the request to the successor -> Option C
  4. Quick Check:

    Missing successor call breaks chain [OK]
Hint: Always pass request to successor if not handled [OK]
Common Mistakes:
  • Forgetting to call successor.handle()
  • Assuming can_handle always returns True
  • Ignoring successor initialization
5. You are designing a logging system using the Chain of Responsibility pattern. You want to handle logs of different severity: DEBUG, INFO, WARNING, ERROR. Each handler should process logs at its level and pass higher severity logs down the chain. Which design best fits this requirement?
hard
A. Each handler processes only its level and passes all logs to the next handler
B. Each handler processes logs at its level and passes only higher severity logs to the next handler
C. Each handler processes all logs regardless of severity and stops the chain
D. Each handler processes logs randomly without order

Solution

  1. Step 1: Understand the logging severity flow

    Logs should be handled at their level, and higher severity logs should continue down the chain for further handling.
  2. Step 2: Evaluate options for correct chain behavior

    Each handler processes logs at its level and passes only higher severity logs to the next handler matches this: handlers process their level and pass higher severity logs onward. Each handler processes only its level and passes all logs to the next handler passes all logs regardless, which is inefficient. Each handler processes all logs regardless of severity and stops the chain stops chain prematurely. Each handler processes logs randomly without order is random and incorrect.
  3. Final Answer:

    Each handler processes logs at its level and passes only higher severity logs to the next handler -> Option B
  4. Quick Check:

    Process level, pass higher severity [OK]
Hint: Process own level, pass higher severity logs down [OK]
Common Mistakes:
  • Passing all logs without filtering
  • Stopping chain too early
  • Ignoring severity order