Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,051visitors

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
HomeLearnSystem DesignLow-Level Design & Design PatternsMachine Coding Exercises
✓ FreeAdvanced· 8 min read

Design a Circuit Breaker

A CircuitBreaker class built from scratch with closed/open/half-open transitions and a failure-threshold counter — mapped directly onto what Resilience4j actually does internally.

Published September 23, 2026


Design a Circuit Breaker

Circuit Breaker Pattern covers using Resilience4j's production-ready implementation. This lesson builds the same mechanism from scratch, connecting the concept to the library you'd actually reach for.

The CircuitBreaker class

enum State { CLOSED, OPEN, HALF_OPEN }

class CircuitBreaker {
    private State state = State.CLOSED;
    private int failureCount = 0;
    private final int failureThreshold;
    private Instant openedAt;
    private final Duration openDuration; // how long to stay OPEN before trying HALF_OPEN

    synchronized <T> T execute(Supplier<T> call, Supplier<T> fallback) {
        if (state == State.OPEN) {
            if (Duration.between(openedAt, Instant.now()).compareTo(openDuration) >= 0) {
                state = State.HALF_OPEN; // timed transition back to trial mode
            } else {
                return fallback.get(); // still open — fail fast, don't even attempt the call
            }
        }
        try {
            T result = call.get();
            onSuccess();
            return result;
        } catch (Exception e) {
            onFailure();
            return fallback.get();
        }
    }

    private void onSuccess() {
        failureCount = 0;
        state = State.CLOSED; // a successful HALF_OPEN trial call closes the breaker again
    }

    private void onFailure() {
        failureCount++;
        if (state == State.HALF_OPEN || failureCount >= failureThreshold) {
            state = State.OPEN; // trip immediately on a failed trial call, OR once the threshold is crossed from CLOSED
            openedAt = Instant.now();
        }
    }
}

Advertisement

The three transitions, explicitly

  • CLOSED → OPEN: failureCount crosses failureThreshold — the breaker gives up on the dependency and starts fast-failing every call via the fallback, without even attempting the real call.
  • OPEN → HALF_OPEN: a timed transition, not an event-driven one — after openDuration elapses, the next call is allowed through as a trial, to test whether the dependency has recovered.
  • HALF_OPEN → CLOSED or OPEN: the trial call's outcome decides everything — success resets failureCount and fully closes the breaker; failure immediately reopens it (no second chance), resetting the open timer.

Mapping this directly to Resilience4j

This hand-rolled version is a deliberately simplified core of what Resilience4j's CircuitBreaker actually implements — the real library adds a sliding window (tracking failure rate over recent calls, not a simple cumulative counter, so an old failure eventually stops counting against the current state) and configurable wait duration in open state (matching openDuration here) — but the fundamental three-state machine and the execute()-wrapping-a-call-with-a-fallback shape is identical. Recognizing this mapping explicitly — "this is what CircuitBreaker.decorateSupplier() is actually doing internally" — is a stronger interview answer than treating the library as a black box.

A thread-safe breaker with a sliding failure-rate window

A breaker that counts consecutive failures is easy to explain, but it behaves poorly under real traffic: one success resets it, even at a 60% error rate, and under heavy load a handful of failures can trip it on noise. Production breakers use a failure rate over a sliding window, a minimum number of calls before judging, and a limited number of trial calls in HALF_OPEN. All of it must be thread-safe, because many request threads call through the same breaker at once.

public final class CircuitBreaker {
    public enum State { CLOSED, OPEN, HALF_OPEN }

    private final int windowSize;                 // last N calls
    private final int minimumCalls;               // don't judge on too few samples
    private final double failureRateThreshold;    // e.g. 0.5 = 50%
    private final Duration openDuration;
    private final int halfOpenTrials;
    private final Clock clock;

    private State state = State.CLOSED;
    private final boolean[] outcomes;             // ring buffer: true = failure
    private int index, recorded, failures;
    private Instant openedAt;
    private int trialsInFlight, trialSuccesses;

    public CircuitBreaker(int windowSize, int minimumCalls, double failureRateThreshold,
                          Duration openDuration, int halfOpenTrials, Clock clock) {
        this.windowSize = windowSize; this.minimumCalls = minimumCalls;
        this.failureRateThreshold = failureRateThreshold; this.openDuration = openDuration;
        this.halfOpenTrials = halfOpenTrials; this.clock = clock;
        this.outcomes = new boolean[windowSize];
    }

    public <T> T call(Supplier<T> action, Function<Throwable, T> fallback) {
        if (!tryAcquirePermission()) return fallback.apply(new CallNotPermittedException(state));
        try {
            T result = action.get();
            onResult(false);
            return result;
        } catch (RuntimeException e) {
            onResult(true);
            return fallback.apply(e);
        }
    }

    private synchronized boolean tryAcquirePermission() {
        if (state == State.OPEN && !clock.instant().isBefore(openedAt.plus(openDuration))) {
            state = State.HALF_OPEN; trialsInFlight = 0; trialSuccesses = 0;       // time's up: allow trials
        }
        if (state == State.CLOSED) return true;
        if (state == State.HALF_OPEN && trialsInFlight < halfOpenTrials) { trialsInFlight++; return true; }
        return false;                                                              // OPEN, or trials exhausted
    }

    private synchronized void onResult(boolean failed) {
        switch (state) {
            case HALF_OPEN -> {
                if (failed) trip();                                                 // any trial failure: reopen
                else if (++trialSuccesses >= halfOpenTrials) reset();               // all trials passed: close
            }
            case CLOSED -> {
                if (recorded == windowSize && outcomes[index]) failures--;         // evict the oldest outcome
                outcomes[index] = failed;
                if (failed) failures++;
                index = (index + 1) % windowSize;
                recorded = Math.min(recorded + 1, windowSize);
                if (recorded >= minimumCalls && (double) failures / recorded >= failureRateThreshold) trip();
            }
            case OPEN -> { }                                                        // a late result from before tripping
        }
    }

    private void trip()  { state = State.OPEN; openedAt = clock.instant(); }
    private void reset() { state = State.CLOSED; index = recorded = failures = 0; java.util.Arrays.fill(outcomes, false); }
}

What each piece prevents:

  • The ring buffer keeps the last N outcomes in O(1) per call and O(N) memory, so the rate reflects recent behaviour, not history from an hour ago.
  • minimumCalls: two failures out of the first three calls after startup shouldn't open the circuit.
  • Limited HALF_OPEN trials: without a cap, every waiting request floods the recovering service the moment the timer expires, often knocking it straight back down.
  • Synchronized state transitions keep concurrent callers from seeing half-updated state. The protected call itself runs outside the lock, so slow calls don't serialize traffic.
  • The injected Clock makes the OPEN → HALF_OPEN timing testable without sleeping.

What counts as a failure?

Not every exception means the dependency is unhealthy:

  • Count: timeouts, connection errors, HTTP 5xx and 429 responses.
  • Don't count: 4xx client errors (a bad request from your code or your user says nothing about the service's health), or business exceptions like "insufficient funds".
  • Slow calls: a service answering in 8 seconds is effectively down. Resilience4j can treat calls slower than a threshold as failures (slowCallDurationThreshold, slowCallRateThreshold).

Composing with timeouts, retries and bulkheads

A breaker is one part of a resilience stack, and the ordering matters. A typical arrangement from outermost to innermost is retry → circuit breaker → bulkhead → timeout → call:

  • The timeout makes slow calls fail fast, so they're counted.
  • The bulkhead (a concurrency limit) stops one slow dependency from consuming every thread.
  • The breaker stops calling a dependency that's clearly failing.
  • Retries sit outside the breaker, so that when the circuit is open, retries fail instantly instead of hammering the dependency. Retry only idempotent operations, with backoff and jitter.

Follow-up questions this topic invites — and their answers

Q: Why does a single failed HALF_OPEN trial call reopen the breaker immediately, rather than requiring several trial failures? A: HALF_OPEN exists specifically to probe cautiously — allowing only one (or a small, configurable number of) trial call(s) through avoids sending a burst of traffic back at a possibly-still-struggling dependency; immediately reopening on any trial failure is the conservative, safe default, trading a slightly slower recovery detection for not risking overwhelming a barely-recovering service.

Q: What's the actual difference between a simple failure COUNT threshold and Resilience4j's sliding window RATE? A: A raw count (as implemented above) never 'forgets' old failures within CLOSED state until a success resets it entirely — a sliding window instead considers only the last N calls (or calls within a time window), so a service that failed 5 times an hour ago but has succeeded consistently since doesn't still count those old failures toward tripping the breaker now, which is a meaningfully more accurate signal of CURRENT health.

Q: Should the fallback itself be allowed to fail? A: It should be designed to be extremely reliable and simple (a cached value, a static default, a graceful degradation message — see Circuit Breaker Pattern's fallback design options) precisely because it's the last line of defense; a fallback that can itself throw defeats the entire purpose of the breaker, which exists to guarantee SOME response rather than an unhandled exception.

Q: How would you unit test the OPEN → HALF_OPEN timed transition without actually waiting openDuration in a test? A: Inject a Clock (or a time-supplier abstraction) instead of calling Instant.now() directly, letting a test advance a fake clock instantly rather than sleeping — the same dependency-injection-for-testability principle applied to time-dependent logic throughout this course (e.g. Producer-Consumer Class Design's injectable Dice-equivalent reasoning).

Q: Why use a failure rate over a window instead of a consecutive-failure count? A: Consecutive counts reset on any success, so a service failing 70% of the time may never trip the breaker. A sliding-window rate, with a minimum number of calls, reflects recent health accurately, and avoids tripping on a few unlucky calls.

Q: Why limit the number of calls allowed in HALF_OPEN? A: A recovering service is fragile. Letting all queued traffic through the instant the open period ends can overload it again. A few trial calls decide whether to close or reopen, while everything else keeps using the fallback.

Q: Should 4xx errors count as failures? A: Usually not. They indicate a problem with the request, not with the dependency's health. Counting them would let invalid user input open the circuit for everyone. Count timeouts, connection failures, 5xx and throttling responses.

Advertisement

Previous

Design a Job Scheduler

Next

Design a Distributed ID Generator

AI Tutor

Lesson: Design a Circuit Breaker

Quick actions

AI responses can be inaccurate. Verify critical information.