ReviseAlgo Logo

Behavioral Patterns

Chain of Responsibility

Pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.

Last Updated: June 26, 2026 23 min read

The Chain of Responsibility Pattern is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain. This pattern decouples senders and receivers, allowing you to dynamically add, remove, or reorder middleware logic at runtime.

1. Learning Objectives

  • Identify and refactor sequential validation structures into modular handler chains.
  • Differentiate between the mechanics of short-circuiting chains and complete pipeline traversals.
  • Evaluate the memory overhead of recursive call stacks in deep handler chains.
  • Detect and prevent cyclic linkages inside dynamic handler graphs.
  • Construct thread-safe API gateways in Java, Python, and C++ using smart pointer linking.

2. Problem & Naive Solution

Suppose you are building a custom API Gateway. Before handling incoming HTTP requests, the gateway must perform several validations:

  • RateLimiting: Drop requests if a client exceeds their daily quota.
  • Authentication: Verify that the Authorization header contains a valid token.
  • PayloadValidation: Inspect the JSON body to ensure required fields are present.

The Naive Solution

A developer might implement these validations using a single monolithic class containing sequential methods:

This direct integration introduces several design flaws:

  • Violates Open/Closed Principle: Adding a new filter check (e.g. IpBlacklistCheck or CachingFilter) forces you to modify the core ApiGateway class, risking regressions.
  • Lack of Reusability: You cannot reuse the authentication check in other services (like internal file servers) because the code is tightly bound to the ApiGateway routing flow.
  • Static Execution Order: The validation sequence is hardcoded. You cannot reorder steps or bypass checks (e.g., bypassing authentication for public API endpoints) dynamically.

3. Issues

Monolithic pipelines make customization difficult. If different routes require different security checks, you must write separate gate classes or clutter the validation methods with complex parameter routing, introducing bugs.

4. Pattern Introduction & UML

The Chain of Responsibility Pattern extracts validation checks into separate classes (handlers). Each handler implements a common Handler interface and holds a reference to the next handler in the chain. When a request arrives, the handler either processes and short-circuits it (e.g. dropping it if rate limits are exceeded) or forwards it to the next link by calling next.handle().

UML: Gateway Filter Chain

5. Participants

  • Handler (Handler): Abstract class declaring the handler contract and maintaining a reference to the next handler.
  • Concrete Handler (RateLimitHandler, AuthHandler): Implements the request processing logic. Decides whether to handle the request or forward it along the chain.
  • Client (ApiGateway): Configures the handler chain and passes requests to the first link.

6. Theory (Short-Circuiting vs. Pipelines & Comparisons)

You can configure request propagation in two ways:

  • Short-Circuit Chain (Fail-Fast): If a handler resolves a request (or encounters a failure), it stops the chain and returns immediately (e.g., dropping requests on authentication failure). This is standard for security gateways.
  • Pipeline Chain (Pass-Through): Handlers process the request and always forward it to the next link (e.g., logging systems where all handlers record messages).
  • Comparisons: - Chain of Responsibility: Hands off execution sequentially. Senders do not know which receiver will ultimately handle the request. - Decorator: Wraps an object to enrich its behavior, matching the identical interface contract. - Composite: Models parent-child tree hierarchies, executing tasks recursively down all branches.

7. Syntax Explanation

Linking handlers requires solid reference management:

  • Java: Declares a protected member variable referencing the next link, returning the linked instance from the setter method to support method chaining (handler.setNext(h1).setNext(h2)).
  • Python: Leverages dynamic duck typing. You can build handlers simply by overriding the __call__ method to route execution.
  • C++: Uses std::shared_ptr to link handlers sequentially, preventing memory leaks when deleting chains.

8. Step-by-Step Implementation

  1. Step 1: Create the abstract Handler base class holding the reference pointer to the next link.
  2. Step 2: Implement helper methods (setNext()) on the base class to support method chaining.
  3. Step 3: Create concrete handler classes overriding the handle() method.
  4. Step 4: Inside each handler, check conditions. If valid, forward the call to the next handler (next.handle()).
  5. Step 5: Assemble the chain inside the client, injecting requests into the first link.

9. Complete Code (Mini Project)

10. Code Walkthrough

Let's review the pipeline mechanics:

  • Method Chaining Builder: The base handler's setNext() method returns the next handler reference. This allows you to assemble chains cleanly using method chaining (e.g. head.setNext(h1).setNext(h2)).
  • Fail-Fast Short-Circuiting: If a handler condition fails (e.g. IpBlacklistHandler detects a blacklisted IP), it returns false immediately, short-circuiting the remaining checks.
  • Polymorphic Delegation: Subclasses use super.handle(request) to forward execution to the next link. The subclasses remain decoupled from one another.

11. Execution Flow

  1. Chain Assembly: The client links the handlers sequentially to form the chain.
  2. Request Ingestion: The client passes the request object to the first handler (filterChainHead.handle()).
  3. Condition Check: The handler evaluates the request. If valid, it forwards the execution along the chain (super.handle()).
  4. Short-Circuit (Optional): If a validation step fails, the handler aborts execution and returns false, halting the chain.
  5. Approval: If the request traverses the entire chain successfully, it returns true.

12. Internal Working (Recursion & Memory Footprints)

Traversing deep handler chains has stack memory implications:

  • Stack Frame Allocation: Every handler in the chain adds a frame to the execution stack during delegation (next.handle()). If the chain is deep (e.g. dozens of filters), the execution stack grows, risking StackOverflowError if stack limits are exceeded.
  • Reference Memory Layout: The handlers form a linked-list hierarchy on the heap. While the memory footprint of individual handlers is minimal, cyclic references (e.g., a handler referencing an ancestor node as its next link) will trigger infinite loops.

13. Complexity Analysis

  • Time Complexity: $O(L)$ where $L$ is the number of active links in the handler chain. Each handler is visited at most once.
  • Space Complexity: $O(L)$ stack memory space due to recursive method delegation.

14. Best Practices

  • Check for Cycles: Implement cycle detection checks when linking handlers to prevent infinite routing loops.
  • Define Fallback Terminals: Always set a default terminal handler at the end of the chain to process requests that traverse all validations successfully.

15. Common Mistakes

  • Infinite Traversal Loops: Accidendally linking a handler back to a previous node in the chain, causing endless recursion.
  • Synchronous Blocking Filters: Running slow, blocking tasks (e.g. database lookups) synchronously inside filter handlers, stalling request throughput.

16. Framework Usage

  • Java Servlet Filters: The standard FilterChain uses the Chain of Responsibility pattern. Servlets intercept requests sequentially to validate sessions, log metrics, and check credentials before routing to controllers.
  • Spring Security: Uses a chain of security filters (SecurityFilterChain) to process authentication, CSRF checks, and role validations on web requests.

17. Interview Discussion

Q: What is the main design difference between the Chain of Responsibility and Decorator patterns?
Answer: - Chain of Responsibility: A request is passed along a chain of independent handler classes. Senders do not know which class will ultimately process the request. - Decorator: Wraps an object to enrich its behavior, matching the identical interface contract. Senders target the wrapper directly.
Q: How do you prevent thread locks when executing database validations inside handler chains?
Answer: Offload blocking checks asynchronously to background threads, or use non-blocking reactive frameworks (like Spring WebFlux) to handle filter loops.
Q: What is the benefit of returning this from the setNext() method?
Answer: It enables Method Chaining syntax. This allows developers to construct chains in a single statement (e.g., chain.setNext(auth).setNext(validation)), making code cleaner.

18. Practice Exercises

  • Easy: Write a Python program containing a custom logging chain with ConsoleLogger, FileLogger, and ErrorLogger handlers.
  • Medium: Design a PurchaseApprovalSystem where requests are routed along a chain of managers based on cost (Manager -> Director -> CEO).
  • Hard: Build a parser validation pipeline for a tax application. The parser validates fields sequentially, aggregating errors and returning if a critical step fails.

19. Challenge Problem

Design an Enterprise Customer Service Ticket Routing Hub. Incoming tickets have different priorities (Low, Medium, High) and categories (Billing, Hardware, Software). The routing hub must pass tickets along a chain of support representatives. If a representative cannot resolve the ticket, it is escalated to the next level (Tier 1 Support -> Tier 2 Engineer -> Support Director). Write this routing engine in Java, Python, or C++ and test escalation flows for different ticket categories.

20. Summary & Cheat Sheet

  • Chain of Responsibility passes requests sequentially along a chain of handlers.
  • Decouples request senders from receivers, enabling dynamic chain adjustments at runtime.
  • Use copy-on-write or method chaining syntax to construct chains cleanly.
  • Always set terminal fallback handlers and implement cycle detection.

21. Quiz

1. What is the primary purpose of the Chain of Responsibility pattern?

A) To adapt incompatible interfaces
B) To pass requests sequentially along a chain of handlers, decoupling senders from receivers (Correct)
C) To manage class instantiation pools

2. Which pattern is structurally similar to Chain of Responsibility but differs in intent by wrapping objects directly?

A) Command Pattern
B) Decorator Pattern (Correct)
C) State Pattern

3. What is a "Short-Circuiting" chain?

A) A chain that loops infinitely
B) A fail-fast chain that stops execution and returns immediately when a handler rejects the request (Correct)
C) A parallel execution pool

4. What memory hazard is introduced by deep recursive handler chains?

A) Heap fragmentation
B) Stack Overflow due to recursive method stack frames (Correct)
C) Thread deadlock

5. Which Java framework uses the Chain of Responsibility pattern to secure web endpoints?

A) Spring Security Filter Chain (Correct)
B) Hibernate JPA
C) Jackson JSON Parser

6. What design rule is violated by having a handler class instance reference a previous node in the chain?

A) Open/Closed Principle
B) Single Responsibility Principle / No circular dependencies (Correct)
C) Liskov Substitution Principle

7. What is the time complexity of a request traversing a chain of $L$ handlers?

A) $O(\log L)$
B) $O(L)$ (Correct)
C) $O(1)$

8. Why does the setNext() method typically return the next handler instance?

A) To satisfy compiler return types
B) To support method chaining builder syntax when assembling the chain (Correct)
C) To force garbage collection of old handlers

9. In C++, how do you prevent raw pointer dangling issues when linking handlers in a chain?

A) Use global arrays
B) Use std::shared_ptr to manage handler lifetimes (Correct)
C) Avoid using pointers

10. Does the Chain of Responsibility pattern support the Open/Closed Principle?

A) Yes, because you can introduce new handlers without modifying existing handler code (Correct)
B) No, because adding links forces rewriting all transition logic
C) Only when using Servlet containers

22. Next Lesson Preview

In the next lesson, we will explore the Visitor Pattern. We will learn how to separate algorithms from the objects on which they operate, allowing you to add new operations to existing class structures without modifying them!