Behavioral PatternsChain of ResponsibilityHard⏱️ ~3 min

Chain of Responsibility: Trade-offs and When to Use

When to Use Chain of Responsibility

The Chain of Responsibility pattern is appropriate in specific scenarios where request processing benefits from decoupling and flexibility.

Use when: You have multiple objects that can handle a request, but you do not know which one should handle it until runtime. For example, in an approval workflow, the appropriate approver depends on the request amount, not predetermined at compile time.

Use when: You want to issue a request to one of several objects without specifying the receiver explicitly. The client should not need to know the internal chain structure or which handler will ultimately process the request.

Use when: The set of handlers and their order should be configurable dynamically. For example, a content moderation system might enable or disable certain filters based on user settings or regional regulations.

Use when: You need to process a request through multiple stages where each stage has a single, well-defined responsibility. Middleware pipelines in web frameworks exemplify this: authentication, then authorization, then rate limiting, then request handling.

Avoid When (Anti-patterns)

Avoid if: Every request will be handled by the same object. If you have deterministic routing logic (for example, a simple if-else or switch statement suffices), the pattern adds unnecessary complexity. Just call the appropriate handler directly.

Avoid if: The chain is very long and performance is critical. Each handler adds overhead through method calls and condition checks. If you have 20+ handlers in a chain processing thousands of requests per second, consider alternative patterns like a lookup table with direct handler access.

Avoid if: Handlers have complex interdependencies. If HandlerB needs to know what HandlerA did, or handlers need to share state extensively, the pattern breaks down. Chain of Responsibility assumes handlers are independent. If they are not, use Composite or Mediator patterns instead.

Avoid if: Request processing order does not matter or handlers can work in parallel. Chain of Responsibility enforces sequential processing. If handlers can execute concurrently, use a different approach like parallel processing pipelines or event-driven architecture.

Comparison with Alternative Patterns

Chain of Responsibility
Handlers linked in sequence. Each decides to process or forward. Client knows only first handler.
vs
Command Pattern
Encapsulates requests as objects. Client knows specific command to execute. No forwarding logic.
Chain of Responsibility
Handlers are independent. Each has single responsibility. Linear chain structure.
vs
Decorator Pattern
Wrappers add behavior. All decorators process request. Nested structure, not chain.
Chain of Responsibility
Handlers linked linearly. Request flows one direction. One or more handlers process.
vs
Mediator Pattern
Central mediator coordinates all handlers. Handlers communicate through mediator, not directly.

Trade-offs and Considerations

Advantage - Flexibility: You can add or remove handlers at runtime without modifying client code. This makes the system highly adaptable to changing requirements.

Advantage - Single Responsibility: Each handler focuses on one concern, making the codebase easier to understand, test, and maintain.

Disadvantage - Debugging Difficulty: When a request is not handled, tracing through the entire chain to find why can be challenging. Consider adding comprehensive logging at each handler.

Disadvantage - No Guaranteed Handling: A request might traverse the entire chain without being processed. You must explicitly handle this case, either by having a default handler at the end or by throwing an exception if the request remains unprocessed.

Disadvantage - Performance Overhead: Every handler in the chain incurs a method call. For high-throughput systems with long chains, this can become a bottleneck. Profile your application to ensure the overhead is acceptable.

Interview Tip: Be ready to discuss when Chain of Responsibility is overkill. For example, if you have only two handlers that never change, a simple if-else is clearer. Always justify pattern usage based on actual flexibility needs, not theoretical future requirements. Premature abstraction is as harmful as premature optimization.

Decision Framework

Choose Chain of Responsibility if: Handlers are independent, order matters, the chain changes dynamically, and you need to decouple sender from receiver.

Choose Command if: You need to parameterize objects with operations, queue requests, or support undo/redo functionality. Commands are more about encapsulating actions, not forwarding decisions.

Choose Decorator if: You need to add responsibilities to objects dynamically and all wrappers should process the request, not conditionally forward it.

Choose Mediator if: Handlers need to communicate with each other bidirectionally or have complex coordination logic. Mediator centralizes communication, while Chain of Responsibility keeps it linear and one-directional.

💡 Key Takeaways
Use when multiple objects can handle a request but handler is unknown until runtime
Appropriate for configurable processing pipelines with independent handlers
Avoid if all requests go to same handler or if handlers have complex interdependencies
Trade-off: flexibility and SRP vs debugging difficulty and performance overhead
Not the same as Command (encapsulation), Decorator (all process), or Mediator (central coordination)
📌 Examples
1Good use: Approval workflow with varying authority levels based on request amount
2Bad use: Two-handler authentication where handler is always deterministic
3Good use: Content moderation with pluggable filters that change by region
4Bad use: Complex handlers that need to share extensive state or communicate bidirectionally
← Back to Chain of Responsibility Overview