Skip to main content

Design a Circuit Breaker

Problem Statement

Design a client-side Circuit Breaker framework (similar to Hystrix or Resilience4j). The circuit breaker acts as a protective wrapper around fragile remote service integration points. It monitors execution failures and prevents cascading network issues by transitions through three states: CLOSED (normal flow), OPEN (fail-fast and fallback execution), and HALF_OPEN (probing recovery).

Asked In Companies

Functional Requirements

  • Support three distinct states: CLOSED, OPEN, and HALF_OPEN.
  • Provide a wrapper method execute(supplier, fallback) that routes execution or fails fast to a fallback immediately.
  • Transition from CLOSED to OPEN if consecutive execution failures exceed a configured threshold.
  • Transition from OPEN to HALF_OPEN after a configured recovery cooldown timeout window.
  • In the HALF_OPEN state, allow a limited number of test requests. If all test requests succeed, close the circuit; if any call fails, reopen the circuit.
  • Ensure thread safety for state transitions and counter evaluations.

Objects Required

  • CircuitBreakerState (State design pattern interface)
  • ClosedState (Logic when remote calls operate normally)
  • OpenState (Logic when remote calls fail fast)
  • HalfOpenState (Logic when testing service recovery)
  • CircuitBreaker (Context coordinator maintaining current configuration)

CircuitBreakerState Interface

We apply the **State Design Pattern** by modeling the behavior rules inside a common CircuitBreakerState interface. This keeps our code modular and prevents massive nested if-else structures.


import java.util.function.Function;
import java.util.function.Supplier;

public interface CircuitBreakerState {
    <T> T execute(Supplier<T> supplier, Function<Throwable, T> fallback);
}

The execute() method is generic. It takes a functional Supplier containing the remote task logic, and a fallback handler Function that returns a rescue payload if the task raises an exception.


CircuitBreaker Context Class

The CircuitBreaker maintains configuration metrics (failure threshold, recovery duration) and delegates its core routing rules to the active polymorphic CircuitBreakerState.


import java.util.function.Function;
import java.util.function.Supplier;

public class CircuitBreaker {
    private CircuitBreakerState state;
    private final int failureThreshold;
    private final long recoveryTimeoutMs;
    private final int trialSuccessThreshold;

    private int consecutiveFailures = 0;
    private int consecutiveSuccesses = 0;
    private long lastStateChangedTime;

    public CircuitBreaker(int failureThreshold, long recoveryTimeoutMs, int trialSuccessThreshold) {
        this.failureThreshold = failureThreshold;
        this.recoveryTimeoutMs = recoveryTimeoutMs;
        this.trialSuccessThreshold = trialSuccessThreshold;
        this.lastStateChangedTime = System.currentTimeMillis();
        this.state = new ClosedState(this);
    }

    public synchronized void setState(CircuitBreakerState state) {
        this.state = state;
        this.lastStateChangedTime = System.currentTimeMillis();
        this.consecutiveFailures = 0;
        this.consecutiveSuccesses = 0;
        System.out.println("Circuit Breaker transitioned to: " + state.getClass().getSimpleName());
    }

    public <T> T execute(Supplier<T> supplier, Function<Throwable, T> fallback) {
        return state.execute(supplier, fallback);
    }

    // --- State and Counter Mutators ---
    public synchronized void recordSuccess() {
        consecutiveSuccesses++;
        consecutiveFailures = 0;
    }

    public synchronized void recordFailure() {
        consecutiveFailures++;
        consecutiveSuccesses = 0;
    }

    public synchronized int getConsecutiveFailures() { return consecutiveFailures; }
    public synchronized int getConsecutiveSuccesses() { return consecutiveSuccesses; }
    
    public int getFailureThreshold() { return failureThreshold; }
    public long getRecoveryTimeoutMs() { return recoveryTimeoutMs; }
    public int getTrialSuccessThreshold() { return trialSuccessThreshold; }
    public long getLastStateChangedTime() { return lastStateChangedTime; }
}

Here is an explanation of the core operations in the CircuitBreaker class:

  • The constructor configures thresholds and initiates the default state to ClosedState.
  • setState() changes the active state, updates timestamps, resets operational counters, and prints debugging markers. It is synchronized to prevent concurrent state transitions.
  • execute() acts as a simple pass-through that delegates the task directly to the current state implementation.
  • recordSuccess() and recordFailure() manage thread-safe counter increments, which are evaluated by the state instances.

ClosedState Class

The ClosedState represents normal operating conditions. Remote tasks are attempted, and failures are recorded.


import java.util.function.Function;
import java.util.function.Supplier;

public class ClosedState implements CircuitBreakerState {
    private final CircuitBreaker cb;

    public ClosedState(CircuitBreaker cb) {
        this.cb = cb;
    }

    @Override
    public <T> T execute(Supplier<T> supplier, Function<Throwable, T> fallback) {
        try {
            T result = supplier.get();
            cb.recordSuccess();
            return result;
        } catch (Throwable throwable) {
            cb.recordFailure();
            if (cb.getConsecutiveFailures() >= cb.getFailureThreshold()) {
                cb.setState(new OpenState(cb));
            }
            return fallback.apply(throwable);
        }
    }
}

The execute() method executes the task. On success, it calls recordSuccess(). On exception, it records the failure. If failures exceed limits, it transitions the breaker context to OpenState and runs the recovery fallback.


OpenState Class

The OpenState prevents network requests from reaching struggling downstream dependencies. It immediately executes fallbacks, but checks timeouts on each call to see if it can attempt recovery.


import java.util.function.Function;
import java.util.function.Supplier;

public class OpenState implements CircuitBreakerState {
    private final CircuitBreaker cb;

    public OpenState(CircuitBreaker cb) {
        this.cb = cb;
    }

    @Override
    public <T> T execute(Supplier<T> supplier, Function<Throwable, T> fallback) {
        long elapsed = System.currentTimeMillis() - cb.getLastStateChangedTime();
        
        if (elapsed >= cb.getRecoveryTimeoutMs()) {
            // Cool-down period elapsed, transition to HALF_OPEN and retry execution
            cb.setState(new HalfOpenState(cb));
            return cb.execute(supplier, fallback);
        }

        // Circuit is wide open, execute fallback directly without calling downstream service
        return fallback.apply(new RuntimeException("Circuit Breaker is OPEN. Fail-fast active."));
    }
}

The execute() method checks if the recovery timeout has elapsed. If so, it updates the state to HalfOpenState and calls cb.execute(...) recursively. If the cooldown is still active, it bypasses the supplier completely and fires the fallback wrapper.


HalfOpenState Class

The HalfOpenState acts as a testing phase. It permits a limited number of executions to probe the health of the downstream service.


import java.util.function.Function;
import java.util.function.Supplier;

public class HalfOpenState implements CircuitBreakerState {
    private final CircuitBreaker cb;

    public HalfOpenState(CircuitBreaker cb) {
        this.cb = cb;
    }

    @Override
    public <T> T execute(Supplier<T> supplier, Function<Throwable, T> fallback) {
        try {
            T result = supplier.get();
            cb.recordSuccess();
            
            if (cb.getConsecutiveSuccesses() >= cb.getTrialSuccessThreshold()) {
                cb.setState(new ClosedState(cb));
            }
            return result;
        } catch (Throwable throwable) {
            // Any failure in HALF_OPEN resets the timer and reopens the circuit
            cb.recordFailure();
            cb.setState(new OpenState(cb));
            return fallback.apply(throwable);
        }
    }
}

The execute() method attempts the execution. If it succeeds, the count updates, and if successes cross the trial threshold, it restores the state to ClosedState. If a single exception is caught, it instantly flips back to OpenState, starting the cooldown timer fresh.


Class Diagram

CircuitBreakerStateexecute(supplier: Supplier<T>, fallback: Function<Throwable, T>): TCircuitBreakerstate: CircuitBreakerStatefailureThreshold: intrecoveryTimeoutMs: longtrialSuccessThreshold: intconsecutiveFailures: intconsecutiveSuccesses: intlastStateChangedTime: longexecute(supplier: Supplier<T>, fallback: Function<Throwable, T>): TsetState(state: CircuitBreakerState): voidrecordSuccess(): voidrecordFailure(): voidgetConsecutiveFailures(): intgetConsecutiveSuccesses(): intgetFailureThreshold(): intgetTrialSuccessThreshold(): intgetRecoveryTimeoutMs(): longgetLastStateChangedTime(): longClosedStatecb: CircuitBreakerexecute(supplier: Supplier<T>, fallback: Function<Throwable, T>): TOpenStatecb: CircuitBreakerexecute(supplier: Supplier<T>, fallback: Function<Throwable, T>): THalfOpenStatecb: CircuitBreakerexecute(supplier: Supplier<T>, fallback: Function<Throwable, T>): T11delegates transitionsdelegates transitionsdelegates transitions

Also See

Comments

Popular posts from this blog

Designing a Parking Lot - Low Level Design

Problem Statement Design a parking lot that can handle vehicles entering and leaving while managing parking across multiple floors. Each vehicle should be assigned a suitable parking spot based on its type, and the spot should be freed once the vehicle exits. The design should also support generating a ticket at entry and optionally calculating the parking fee based on the duration of stay. Asked In Companies Amazon Google Microsoft Uber Walmart Flipkart Meta PayPal Oracle Salesforce Adobe Apple Intuit LinkedIn Atlassian Functional Requirements The design should support multiple vehicle types such as bikes, cars, and trucks A vehicle must be assigned a parking spot compatible with its type A parking spot cannot be assigned to more than one vehicle at a time The parking lot should support multiple levels (floors) The design should search and allocate an availa...

Most Frequently Asked Low Level Design(LLD) Interview Questions

Below are the curated list of most commonly asked Low Level Design (LLD) interview problems. Each problem includes a short description and a link to the complete solution with code and class diagrams. Design Parking Lot System The system should handle parking for different vehicle types such as bikes, cars, and trucks. It should manage slot allocation, availability tracking, and entry/exit flow. The design also ensures efficient usage of parking space under varying load conditions. View Solution Design Elevator / Lift System The system should support multiple elevators operating across floors with request handling logic. It focuses on scheduling algorithms to minimize wait time and optimize movement. It also manages direction control and concurrent floor requests. View Solution Design Movie Ticket Booking System The system should allow users to browse movies, select shows, and book seats. It handles seat ...

Software Design Patterns for LLD Interviews: A Complete Guide

Software Design Patterns for LLD Interviews: A Complete Guide In Software Development Engineer (SDE) interviews—especially for mid-level and senior roles—low-level design (LLD) rounds assess your ability to write clean, reusable, maintainable, and extensible code. The foundation of resolving these architectural challenges lies in the standard Gang of Four (GoF) Design Patterns. Rather than memorizing theoretical definitions, interviewers expect you to apply these patterns to real-world scenarios, identifying the trade-offs of each. Below is a comprehensive guide to the 12 most frequently asked design patterns in LLD interviews, categorized by their classification (Creational, Structural, and Behavioral). Each pattern contains a concrete, real-world Java implementation and a detailed breakdown of design decisions. Creational Design Patterns Creational design patterns deal with object creation mechanisms. They abstract the instantiation process, making a system independent of how...