Circuit Breaker Pattern
A three-state switch in front of a failing dependency: build it, trace it, count failures over a window, and know how it differs from a retry.
Overview
A circuit breaker sits between your code and a dependency it calls: another service, a database, a payment provider. While the dependency is healthy, calls pass through. When enough of them fail, the breaker opens and refuses new calls at once, without trying the dependency. After a pause it lets a few trial calls through; if they succeed it closes again. The name is borrowed from the electrical part that cuts the current before the wiring burns. Martin Fowler credits Michael Nygard's book *Release It!* with making the pattern well known.
In the interview you are expected to write the state machine, choose how failures are counted, and explain what the breaker protects against that a timeout or a retry does not. This topic does each of those with a breaker you can trace by hand.
The problem: slow failure
A dependency that is down and answers with an error is cheap. A dependency that is down and answers with nothing is expensive: each call holds a thread, a socket and memory until the timeout fires. With enough traffic, the calling service runs out of threads and fails too, even though nothing is wrong with it. The breaker turns a slow failure into a fast one.
- Cost of one call to a dead dependency — One thread held until the timeout — A state check, then an immediate error
- Threads held by 200 concurrent calls at a 30 s timeout — 200 threads for 30 s each — None
- Effect on requests that do not need the dependency — They queue behind the stuck ones — Unaffected
- Load on the dependency while it recovers — Full traffic plus retries — A few trial calls
The breaker does not replace the timeout. The timeout is what turns a hang into a failure the breaker can count.
The three states
- Closed — Pass through — Failures — The count reaches the threshold
- Open — Refused at once — Time since opening — The wait time has passed
- Half-open — A limited number pass through as trials — Trial results — A trial fails (back to open) or enough succeed (closed)
A trace with a threshold of 3 consecutive failures, a wait of 10 seconds and one trial call. Time is in seconds.
- 0 — Closed — success — 0 — Closed
- 1 — Closed — failure — 1 — Closed
- 2 — Closed — failure — 2 — Closed
- 3 — Closed — failure — 3 — Open (opened at t=3)
- 4 — Open — refused, dependency not called — 3 — Open
- 13 — Open, wait elapsed — trial: failure — 4 — Open (opened at t=13)
- 23 — Open, wait elapsed — trial: success — 0 — Closed
The breaker as code
Three things make the code testable: the clock is passed in, the dependency is passed in as a function, and the refusal is a distinct exception so the caller can tell 'the breaker said no' from 'the dependency failed'.
This version lets every call through while half-open, which is fine for the one-caller trace. Under real traffic the half-open state must cap the number of trial calls; the concurrency question below adds that gate.
Counting failures
'Three failures in a row' is the simplest rule and the weakest: one success in the middle resets it, so a dependency failing half the time never trips it. Production libraries count over a window instead and trip on a failure rate, with a minimum number of calls so that one failure out of one call does not open the breaker.
- Consecutive failures — One counter — N failures with no success between — A 50% failure rate never trips it
- Count-based window — Outcomes of the last N calls (a ring buffer) — Failure rate over those N calls is at or above a threshold, once N calls have been seen — Slow to react when N is large and traffic is low
- Time-based window — Per-second buckets of successes and failures over the last N seconds — Failure rate over the window is at or above a threshold, once enough calls have been seen — Under very low traffic the window may never hold enough calls
Two libraries show the numbers that engineers actually tune. Hystrix (Netflix; now in maintenance mode, and its README points new projects to Resilience4j) trips when a 10-second rolling window holds at least 20 requests and at least 50% of them failed, then sleeps 5 seconds before a single trial request. Resilience4j's defaults are a count-based window of the last 100 calls, a 50% failure-rate threshold, a minimum of 100 calls before the rate is judged, 60 seconds open, and 10 permitted calls in the half-open state. Resilience4j also counts slow calls: by default a call over 60 seconds is slow, and a 100% slow-call rate trips the breaker.
What you will be able to do
- Write the closed, open and half-open state machine and trace it on a timeline.
- Explain why the breaker protects the caller's threads, not only the dependency.
- Choose between consecutive-failure, count-window and time-window counting and say what each misses.
- Place the breaker correctly next to timeouts, retries, fallbacks and bulkheads.
- Name the thresholds a real library exposes and what to watch in production.
Interview questions
- Implement a circuit breaker with the three-state state machine.
- What is the difference between a circuit breaker and a retry with exponential backoff?
- How do you count failures over a sliding window instead of consecutively?
- How does the breaker behave under concurrency, especially in the half-open state?
- How do you tune the thresholds and monitor a breaker in production?
Sign up free to read the full topic
More Low-Level Design topics
Start free on Cariara · Coding problems · System design studio