Bird
Raised Fist0

Chain of Responsibility - Passing Requests Through a Chain of Handlers

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
๐ŸŽฏ
Chain of Responsibility - Passing Requests Through a Chain of Handlers
40 min ยท medium
medium

Design a system where requests pass through a chain of 5 handler levels, each with 4 gates. Each handler decides whether to process or forward the request. Pricing depends on which handler processes the request.

  • There are exactly 5 handler levels arranged sequentially.
  • Each level has 4 gates that can process requests concurrently.
  • Requests must be passed along the chain until handled or rejected.
  • Pricing is determined by the handler level that processes the request.
  • Handlers maintain state about their availability and pricing.
  • The system must support concurrent request processing without race conditions.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define the Handler abstraction
Design the base Handler abstraction that holds state and defines the interface for processing requests and forwarding them.
๐Ÿ’ก Hint
Use an abstract class to hold instance state like nextHandler reference and pricing info, since interfaces cannot hold state in Java.
abstract class Handler with protected volatile Handler nextHandler and abstract synchronized boolean handleRequest(Request req) - abstract class needed because it holds instance state (nextHandler, pricing), interface cannot hold fields in Java.
Mid: Recognizes Chain of Responsibility pattern with abstract Handler class. Senior: Explains why volatile is needed on nextHandler to ensure visibility across threads and synchronized on handleRequest to avoid race conditions when checking and updating handler state.
โš ๏ธCommon Wrong Answer
Using interface Handler with default methods and fields leads to inability to hold instance state like nextHandler reference, causing design to break when chaining handlers.
Step 2
Implement concrete Handler levels with gates
Design concrete Handler subclasses representing each level with 4 gates that can process requests concurrently.
๐Ÿ’ก Hint
Use composition to hold an array of Gate objects inside each Handler level. Gates should have synchronized methods to atomically check and update availability.
HandlerLevel class composes Gate[] gates; Gate has volatile boolean isAvailable and synchronized boolean process(Request req) to atomically check-and-set availability, ensuring thread-safe concurrent processing.
Mid: Identifies composition relationship between HandlerLevel and Gate. Senior: Explains how volatile on isAvailable ensures visibility and synchronized process method prevents two threads from acquiring the same gate simultaneously.
โš ๏ธCommon Wrong Answer
Using a shared boolean without volatile or synchronization on Gate leads to race conditions where multiple threads may acquire the same gate, causing inconsistent processing.
Step 3
Chain the handlers and forward requests
Implement the logic to forward requests along the chain if the current handler cannot process it.
๐Ÿ’ก Hint
Use the nextHandler reference in Handler to forward requests. Synchronize handleRequest to atomically check if current handler can process or forward.
In Handler.handleRequest(Request req), use synchronized method to atomically check gate availability and either process or call nextHandler.handleRequest(req). nextHandler is volatile to guarantee visibility of chain updates.
Mid: Understands forwarding logic in Chain of Responsibility. Senior: Details how synchronized handleRequest prevents race conditions during check-and-forward, and volatile nextHandler ensures thread-safe chain updates.
โš ๏ธCommon Wrong Answer
Not synchronizing handleRequest leads to race conditions where multiple threads may simultaneously decide to forward or process, causing duplicate processing or lost requests.
Step 4
Implement pricing based on handler level
Design how pricing is determined by the handler level that processes the request.
๐Ÿ’ก Hint
Store pricing as a final field in each concrete Handler subclass. Return pricing upon successful processing.
Each concrete HandlerLevel has final int pricing field set in constructor. handleRequest returns pricing if processed, or -1 if forwarded. Pricing is immutable, so no synchronization needed.
Mid: Recognizes pricing is tied to handler level. Senior: Explains immutability of pricing field avoids concurrency issues and simplifies design.
โš ๏ธCommon Wrong Answer
Storing pricing in a mutable shared static field leads to concurrency bugs and incorrect pricing returned under concurrent requests.
Step 5
Ensure thread-safe chain updates and lifecycle management
Design how to safely update the chain of handlers at runtime and manage lifecycle of handlers and gates.
๐Ÿ’ก Hint
Use volatile on nextHandler to ensure visibility of chain updates. Use composition for gates lifecycle; gates cannot exist independently of handler level.
nextHandler declared volatile to guarantee visibility of chain updates across threads. Handler owns gates by composition; gates lifecycle tied to handler level instance.
Mid: Understands volatile usage for visibility. Senior: Explains how without volatile, threads may see stale nextHandler references causing requests to be lost or stuck.
โš ๏ธCommon Wrong Answer
Updating nextHandler without volatile leads to threads seeing stale references, causing requests to be forwarded incorrectly or dropped.
๐Ÿ”Œ
API Design Challenge
Design an API that allows clients to submit requests to the chain and receive pricing information based on which handler processes the request. The API should support concurrent requests and allow dynamic updates to the handler chain.
  • Use a method like handleRequest(Request req) that returns pricing or failure indicator.
  • Ensure thread-safe updates to the handler chain using volatile and synchronization.
  • Design the API to expose handler chain configuration and status for monitoring.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • abstract class Handler with volatile nextHandler and synchronized handleRequest(Request req)
  • Concrete HandlerLevel composes Gate[] gates with volatile isAvailable and synchronized process(Request req)
  • nextHandler declared volatile to ensure visibility of chain updates
  • handleRequest synchronized to prevent race conditions during processing and forwarding
  • Pricing stored as final immutable field in HandlerLevel subclasses
Good to Have - 30%
  • Explanation of composition lifecycle ownership between HandlerLevel and Gate
  • Use of Chain of Responsibility pattern enforcing Single Responsibility and Open/Closed principles
  • Explanation of volatile and synchronized usage for concurrency control
Bonus - 10%
  • Discussion of dynamic chain updates and lifecycle management
  • Details on concurrent request processing optimizations
โœ…
Model Answer
โ–ผ Reveal

This design implements the Chain of Responsibility pattern in Java with a chain of 5 HandlerLevel classes, each containing 4 Gate objects to process requests concurrently. The abstract Handler class holds a volatile nextHandler reference and defines a synchronized handleRequest method to ensure thread-safe processing and forwarding. Pricing is stored as a final immutable field in each HandlerLevel subclass. Gates use volatile isAvailable flags and synchronized process methods to prevent race conditions. The chain supports dynamic updates with volatile nextHandler to guarantee visibility. This design ensures safe concurrent request processing, proper lifecycle management, and clear pricing determination based on the processing handler level.

  • Use abstract class Handler to hold instance state (nextHandler, pricing) because Java interfaces cannot hold fields.
  • Declare nextHandler as volatile to ensure visibility of chain updates across threads.
  • Synchronize handleRequest to atomically check gate availability and forward requests safely.
  • Use composition to hold Gate[] inside HandlerLevel; Gate uses volatile isAvailable and synchronized process method to prevent race conditions.
  • Store pricing as final immutable field in HandlerLevel to avoid concurrency issues.
  • Tie gates lifecycle to HandlerLevel instance via composition to manage lifecycle safely.
  • Design API with handleRequest(Request req) returning pricing or failure indicator, supporting concurrent requests.
  • Use volatile and synchronized together to enforce happens-before relationships and atomicity in concurrent environment.
  • abstract class Handler holds volatile Handler nextHandler and defines synchronized boolean handleRequest(Request req) to atomically process or forward requests.
  • Concrete HandlerLevel classes compose Gate[] gates; Gate has volatile boolean isAvailable and synchronized boolean process(Request req) to ensure thread-safe gate acquisition.
  • nextHandler is volatile to guarantee visibility of chain updates across threads.
  • handleRequest is synchronized to prevent race conditions during processing and forwarding decisions.
  • Pricing is stored as final immutable field in each HandlerLevel subclass to avoid concurrency issues.
  • Composition relationship between HandlerLevel and Gate ensures gates lifecycle is bound to handler level instance.
  • Use of volatile and synchronized together prevents race conditions and visibility bugs in concurrent request processing.
  • Chain of Responsibility pattern enforces Single Responsibility and Open/Closed principles by decoupling request senders and receivers.
  • Avoid using interfaces for Handler abstraction because interfaces cannot hold instance state like nextHandler or pricing in Java.
  • Synchronized methods and volatile fields are used to enforce happens-before relationships and atomicity in concurrent environment.

Practice

(1/5)
1. In designing a TokenBucketRateLimiter class, should the RateLimiter be an abstract class or an interface? Consider that the rate limiter maintains internal token state and exposes tryConsume() behavior.
easy
A. Abstract class, because it can encapsulate shared token state and partial implementation
B. Interface, because it only defines behavior and state is managed externally
C. Interface, because Java interfaces can have private fields for token state
D. Abstract class, because interfaces cannot define any methods

Solution

  1. Step 1: Analyze state and behavior requirements

    RateLimiter needs to maintain internal token count state and provide tryConsume() behavior.
  2. Step 2: Abstract class vs interface for stateful behavior

    Abstract class can hold shared mutable state and provide partial implementation; interfaces cannot hold instance state.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Abstract class supports shared token state and behavior [check]
Hint: Stateful base class -> abstract class, not interface [check]
Common Mistakes:
2. In designing the Vehicle abstraction for the Ride Booking system, should Vehicle be an abstract class or an interface, and why?
easy
A. Abstract class, because Vehicle has common state like licensePlate and shared methods like startEngine().
B. Interface, because Vehicle only defines behavior without any shared state or implementation.
C. Interface, because Java interfaces can now have default methods to share code and state.
D. Abstract class, because interfaces cannot enforce method signatures.

Solution

  1. Step 1: Analyze Vehicle's shared state and behavior

    Vehicle has common fields (e.g., licensePlate) and methods (e.g., startEngine) that require shared implementation and state storage.
  2. Step 2: Abstract class vs interface suitability

    Abstract classes allow shared state and method implementations, while interfaces cannot hold instance state. Thus, abstract class fits Vehicle better.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Shared state + behavior -> abstract class [check]
Hint: Shared state means abstract class, not interface [check]
Common Mistakes:
3. Given a Vehicle abstraction that requires storing state like speed and fuelLevel, and behavior like accelerate(), should Vehicle be an abstract class or an interface? Why?
easy
A. Abstract class, because it can hold shared state and provide default behavior implementations.
B. Interface, because it allows multiple inheritance and enforces behavior contracts without state.
C. Interface, because Java 8+ interfaces can have default methods and fields for state.
D. Abstract class, because it forces all subclasses to implement all methods explicitly.

Solution

  1. Step 1: Analyze state and behavior requirements

    Vehicle needs to hold shared mutable state like speed and fuelLevel, which interfaces cannot hold as fields.
  2. Step 2: Evaluate abstract class vs interface

    Abstract classes can have instance variables and default method implementations, enabling code reuse and shared state. Interfaces cannot hold instance state, only constants.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Stateful abstraction -> abstract class, not interface [check]
Hint: Stateful base -> abstract class; stateless contract -> interface [check]
Common Mistakes:
4. In the Meeting Scheduler, the RoomBookingManager class has a bookRoom() method that checks if a room is available and then marks it booked. Without synchronizing bookRoom(), which exact sequence of events between Thread 1 and Thread 2 causes a double-booking race condition?
medium
A. Thread 1 reads availability as false; Thread 2 reads availability as true; both proceed to book.
B. Thread 1 and Thread 2 both wait on a lock, causing deadlock and no booking.
C. Thread 1 books room; Thread 2 reads availability after booking and cancels its booking.
D. Thread 1 checks isAvailable() -> true, context switch before booking; Thread 2 checks isAvailable() -> true, both book simultaneously.

Solution

  1. Step 1: Identify check-then-act gap

    Without synchronization, Thread 1 reads availability as true but is preempted before booking; Thread 2 also reads true and proceeds.
  2. Step 2: Both threads book simultaneously causing double-booking

    Because the availability check and booking are not atomic, both threads can book the same room concurrently.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Unsynchronized check-then-act -> double-booking race [check]
Hint: Check-then-act without sync -> double-booking race [check]
Common Mistakes:
5. You need to add ElectricCar and ChargingSpot to the existing Abstract Factory design without modifying existing classes. What is the minimum change to support this extensibility in Java?
hard
A. Add ELECTRIC_CAR and CHARGING to existing enums, create ElectricCar and ChargingSpot classes, and update compatibility map; no existing class modified.
B. Modify Vehicle and Spot base classes to include electric-specific fields and methods.
C. Create a new factory subclass for electric vehicles and modify the client code to use it.
D. Add electric-specific logic inside existing Vehicle and Spot classes using instanceof checks.

Solution

  1. Step 1: Identify extensibility requirements

    New vehicle and spot types must be added without modifying existing classes to respect OCP.
  2. Step 2: Apply Java-specific extensibility

    Adding new enum values and creating new classes for ElectricCar and ChargingSpot allows extension without modifying existing classes. Updating compatibility map centralizes compatibility logic.
  3. Step 3: Eliminate violating options

    Options B and D modify existing classes, violating OCP. Create a new factory subclass for electric vehicles and modify the client code to use it. requires client code changes but not existing classes.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    New enums and classes added, existing classes untouched [check]
Hint: Add enums and classes, update map; no existing class changed [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard