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