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
Decouple sender and receiver by passing requests along a chain until handled
mediumDesign Patterns - AppliedFreq: medium
Prerequisites:OOP FundamentalsJava Concurrency BasicsDesign Patterns Basics
⚙️
Functional Requirements
  • Support chaining multiple handlers in a sequence
  • Each handler can either process or forward the request
  • Requests must be passed along the chain until handled or end reached
  • Handlers must be able to inspect and modify requests
  • Thread-safe handling of requests in concurrent environments
  • Ability to dynamically configure or reorder handlers
  • Provide feedback if request is unhandled
👤
Use Cases
  • Client sends a request to the first handler in the chain
  • Handler decides if it can process the request or passes it to the next handler
  • Chain can be dynamically configured with different handlers
  • Request is processed by the first capable handler in the chain
  • If no handler can process the request, it is rejected or logged
  • Handlers can be added or removed without changing client code
🧑‍💻
Actors
  • Client (request sender)
  • Handler (abstract request processor)
  • ConcreteHandler (specific request processors)
❓
Clarifying Questions to Ask
  • How many concurrent threads will send requests simultaneously?
  • Are handlers shared across threads or thread-local?
  • Is the chain static or can it be modified at runtime?
  • What is the expected maximum length of the handler chain?
  • Should handlers maintain any state between requests?
  • What is the pricing or cost model if any for processing requests?
  • How to handle requests that no handler can process?
Out of Scope
✗ Request queueing or asynchronous processing
✗ Complex retry or fallback mechanisms
✗ Distributed chain across multiple JVMs
✗ Persistence of requests or handlers
⚡
QPS Calculation
expected qps1000
average request size kb1
daily request volume86400000
data volume gb per day86.4
🖥️
Server Estimate
servers required3
reasoningBased on 1000 QPS and concurrency overhead, 3 servers provide redundancy and load balancing.
🔢
Enumerations
enum RequestType
TYPE_A | TYPE_B | TYPE_C | UNKNOWN
Defines discrete request categories to avoid string typos and enable exhaustive switch-case handling in handlers.
enum HandlerStatus
IDLE | PROCESSING | FAILED | COMPLETED
Tracks handler processing state with volatile to ensure visibility across threads.
📦
Request
3 fields
private final RequestType typeprivate final String payloadprivate volatile boolean handled
Encapsulate request data and track if it has been handled.
public RequestType getType()Returns the type of the request.
public String getPayload()Returns the request payload.
public synchronized boolean isHandled()Checks if the request has been handled in a thread-safe manner.
public synchronized void markHandled()Marks the request as handled atomically.
📦
Handler
2 fields
protected volatile Handler nextHandlerprotected volatile HandlerStatus status
Abstract base class defining the interface and chaining mechanism for concrete handlers.
public synchronized void setNextHandler(Handler next)Atomically sets the next handler in the chain.
public abstract boolean handleRequest(Request request)Attempts to handle the request or passes it to the next handler.
protected abstract boolean canHandle(Request request)Checks if this handler can process the given request.
📦
ConcreteHandlerA
1 fields
private final RequestType supportedType = RequestType.TYPE_A
Processes requests of TYPE_A or forwards them.
public synchronized boolean handleRequest(Request request)Processes TYPE_A requests or forwards to next handler.
protected boolean canHandle(Request request)Returns true if request type is TYPE_A.
📦
ConcreteHandlerB
1 fields
private final RequestType supportedType = RequestType.TYPE_B
Processes requests of TYPE_B or forwards them.
public synchronized boolean handleRequest(Request request)Processes TYPE_B requests or forwards to next handler.
protected boolean canHandle(Request request)Returns true if request type is TYPE_B.
📦
Client
1 fields
private Handler firstHandler
Sends requests to the first handler in the chain.
public boolean sendRequest(Request request)Sends request to the chain and returns true if handled.
🔗
Relationships
Handler◇──►Handler[0..1]
aggregationnextHandler can exist independently; chain links are mutable
ConcreteHandlerA──►Handler[1]
inheritanceConcreteHandlerA is a subtype of Handler
ConcreteHandlerB──►Handler[1]
inheritanceConcreteHandlerB is a subtype of Handler
Client──►Handler[1]
associationClient holds reference to first handler but does not own it
🗄️
Requests
PK: requestId
requestId UUIDtype RequestTypepayload Stringhandled BooleancreatedAt Timestamp
Stores incoming requests with their status and metadata.
🗄️
Handlers
PK: handlerId
handlerId UUIDhandlerType Stringstatus HandlerStatuslastProcessedRequestId UUID
Stores handler metadata and current processing status.
🔌
POST /requests
Request
{
  "type": "RequestType",
  "payload": "String"
}
Response
{
  "handled": "Boolean",
  "message": "String"
}
Accepts a new request and passes it through the handler chain.
🔌
GET /handlers/{id}/status
Request
{
  "id": "UUID"
}
Response
{
  "status": "HandlerStatus",
  "lastProcessedRequestId": "UUID"
}
Returns the current status of a handler.
🔌
POST /handlers/{id}/next
Request
{
  "nextHandlerId": "UUID"
}
Response
{
  "success": "Boolean",
  "message": "String"
}
Sets the next handler in the chain for a given handler.

Practice

(1/5)
1. A logging framework has a Logger class with a private constructor and a static synchronized getInstance() method returning the single instance. Which design pattern is applied here and why is it the correct choice?
easy
A. Factory pattern, because it controls object creation centrally.
B. Prototype pattern, to clone Logger instances efficiently.
C. Builder pattern, to construct Logger with complex configurations.
D. Singleton pattern, ensuring only one Logger instance exists globally.

Solution

  1. Step 1: Identify the pattern from private constructor and static getInstance()

    The private constructor prevents external instantiation, and the static synchronized getInstance() ensures a single shared instance, which matches the Singleton pattern.
  2. Step 2: Why Singleton is correct here

    Logging frameworks require a single global Logger instance to coordinate log output consistently. Factory, Builder, and Prototype patterns do not enforce single instance semantics.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Private constructor + static synchronized getInstance() -> Singleton [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
2. In designing a Vehicle abstraction, should it be an abstract class or an interface if it needs to hold common state like speed and fuelLevel? Why?
easy
A. Use an interface, because interfaces can hold state and provide default implementations.
B. Use an abstract class, because it can hold instance variables and provide shared behavior.
C. Use an interface, because it enforces implementation without sharing any code or state.
D. Use an abstract class, because interfaces cannot declare methods.

Solution

  1. Step 1: Analyze state and behavior requirements

    Since Vehicle needs to hold common state like speed and fuelLevel, it requires instance variables.
  2. Step 2: Choose abstraction supporting state

    Abstract classes can have instance variables and shared method implementations, whereas interfaces cannot hold instance state (only constants and default methods without state).
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    State requires instance variables -> abstract class [check]
Hint: State needs fields -> abstract class; interfaces only declare behavior [check]
Common Mistakes:
3. In a multi-threaded parking lot system, if parkVehicle() is not synchronized, which exact sequence of Thread 1 and Thread 2 calls causes double-booking of the same spot?
medium
A. Thread 1 checks spot availability -> true, context switch; Thread 2 checks spot availability -> true; both park simultaneously.
B. Thread 1 parks vehicle, Thread 2 waits for Thread 1 to finish, then parks vehicle.
C. Thread 1 parks vehicle, Thread 2 parks vehicle immediately after without checking availability.
D. Thread 1 releases spot, Thread 2 parks vehicle before Thread 1 finishes updating status.

Solution

  1. Step 1: Identify race condition

    Without synchronization, both threads can read spot availability as true before either marks it occupied.
  2. Step 2: Trace exact interleaving

    Thread 1: isAvailable() -> true, context switch; Thread 2: isAvailable() -> true; both proceed to park, causing double-booking.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Check-then-act gap without synchronization -> double-booking [check]
Hint: Check availability then park must be atomic to avoid double-booking [check]
Common Mistakes:
4. To support ElectricCar needing ChargingSpot in the file system's parking analogy, what is the minimum change to the current design to maintain extensibility without modifying existing classes?
hard
A. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility map externally.
B. Modify Vehicle base class to include a boolean isElectric flag and add charging logic inside existing methods.
C. Create a new ElectricCar class and modify ParkingLot to handle charging spots explicitly.
D. Add a new ChargingSpot class inheriting from Spot and modify Vehicle to check for charging compatibility.

Solution

  1. Step 1: Identify extensibility requirements without modifying existing classes

    Adding new vehicle and spot types should not require changing existing class code to avoid violating OCP.
  2. Step 2: Use enums for types and external compatibility map to add new types and compatibility rules

    This allows adding ElectricCar and ChargingSpot by extending enums and maps without modifying existing classes.
  3. Step 3: Create ElectricCar subclass for specific behavior without altering Vehicle base class

    This maintains clean separation and extensibility.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Extend enums + external map + subclass -> minimal change [check]
Hint: Extend enums and maps, subclass new type, no existing class edits [check]
Common Mistakes:
5. To add ElectricCar and ChargingSpot support without modifying existing classes, what is the minimum change to the current Java design?
hard
A. Create a new ElectricCar class with charging logic inside Vehicle, and add ChargingSpot as a subclass of Spot with overrides.
B. Modify Vehicle and Spot classes to add electric-specific fields and methods, then create ElectricCar and ChargingSpot subclasses.
C. Add electric vehicle logic inside existing Vehicle class and charging spot logic inside Spot class, avoiding new enums.
D. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility.COMPATIBILITY_MAP; no existing classes modified.

Solution

  1. Step 1: Identify extensibility without modifying existing classes

    Adding new enum values and new subclasses allows extension without changing existing class code, respecting OCP.
  2. Step 2: Use compatibility map update for new spot-vehicle matching

    Updating SpotCompatibility.COMPATIBILITY_MAP with new types supports new logic without modifying existing classes' internals.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    New enums + subclasses + compatibility map update -> minimal change, no existing class modification [check]
Hint: Add enums + subclasses + compatibility map -> extend without modify [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