In this article
Before you start
This page is one step of the industry coding learning path.
Why interviewers ask this
A circuit breaker looks like a ten-minute exercise and then turns into a test of how you handle time, shared state and unclear requirements. Candidates report being given it in pair-programming rounds, with a request to keep the structures thread-safe and with details left open on purpose so that you have to ask. Treat it as the kind of problem covered in machine coding rounds and low level design questions.
A weak answer draws three boxes and says "after enough failures it stops calling." A strong answer fixes what counts as a failure, over what period, how long the breaker stays open, how many trial calls half-open allows, and what the caller does with a rejection. A Staff-level answer then shows the state change is atomic, so that two threads cannot both decide to probe a struggling service, and that the lock is never held during the remote call.
Circuit breaker pattern from first principles
- Circuit breaker
- A circuit breaker is a proxy around an operation that can fail, usually a remote call, that tracks recent failures and, once they pass a threshold, rejects calls immediately instead of making them, then tests for recovery after a wait.
- Half-open state
- The half-open state is the breaker's testing state, entered when the wait in the open state ends, in which a limited number of calls are let through; success closes the breaker and any failure opens it again.
- Fail fast
- Fail fast is an error-handling rule that returns an error to the caller at once, without attempting the operation, when the operation is likely to fail, so the caller holds no thread, connection or memory while it waits.
In plain words
Think of the fuse box in a house. When a faulty appliance draws too much current, the breaker flips and cuts the circuit, so the wiring does not burn. Someone fixes the appliance and flips it back. The analogy breaks in one place: a software breaker flips itself back, carefully, by trying one small test call after a wait instead of waiting for a person.
In one precise sentence, a circuit breaker is a small state machine that decides, before each call, whether the call is allowed to reach a dependency.
The reason to build one is the cascading failure. A cascading failure is a fault in one component that spreads because callers waiting on it use up shared resources. Fowler describes callers running out of critical resources when a supplier stops responding, and credits Michael Nygard's book Release It with popularizing the pattern.
Step 1: Closed counts recent failures
In the closed state the breaker is transparent: every call goes through to the dependency, and the breaker keeps a record of failures. Input: the result of each call. Output: the same result, plus an updated failure record.
The record must be recent. The Microsoft Azure Architecture Center says the failure counter in the closed state is time based and resets at periodic intervals, so that occasional failures do not open the breaker. A sliding window is a rule that counts only the events inside the most recent stretch of time (or the most recent number of calls). A simple version keeps the timestamp of each failure and drops those older than the window.
When the count inside the window reaches the threshold, the breaker trips: it records the time and moves to open. You now know when the breaker opens; the next step is what it does while open.
Step 2: Open rejects calls without touching the dependency
In the open state the breaker rejects every call immediately and raises an error that says "the circuit is open." Input: a call. Output: a fast error, with no call made to the dependency. That is what failing fast means in code.
Two things make this useful. The caller waits for no timeout, so its threads and connections are free. The struggling dependency also gets no new load while it recovers. Fowler notes that a broken circuit avoids putting load on a struggling server.
The open state lasts for a fixed wait. When a call arrives and the wait is over, the breaker moves to half-open. It does not need a background timer: comparing the current time with the stored trip time on each call is enough, and is easier to test with a fake clock. Microsoft also describes a variant that increases the wait if the fault persists, starting with a few seconds and growing to minutes.
Step 3: Half-open lets one trial call through, under a lock
The half-open state lets a limited number of trial calls reach the dependency. If they succeed, the breaker closes and clears its failure record; if any fails, it opens again and restarts the wait. Microsoft says this limit stops a recovering service from being flooded with requests.
"Limited" is the part that needs care. Many threads call the breaker at once. If each thread reads "half-open, no trial running" and only then marks a trial as started, several threads read the same answer and all become trials. The fix is to make the check and the mark a single step under a lock. Microsoft warns that the implementation should not block concurrent requests or add excessive overhead, so hold the lock only for the bookkeeping and release it before the remote call.
You now know the whole cycle. The last step is what the caller does with the breaker's answer.
Step 4: The caller handles the rejection
A breaker does not solve the failure; it moves it. Fowler says clients need to react to breaker failures: a credit card authorization could be put on a queue for later, and missing data could be replaced by stale data that is good enough to show. Microsoft lists the same options: degrade functionality, call an alternative, or tell the user to try later.
Two rules follow. Keep one breaker per dependency, so that trouble in one service does not block calls to a healthy one; Microsoft warns that merging errors from independent providers, such as shards of one data store, can block healthy ones. Second, an open-circuit error is a signal to stop retrying that call, not to retry it harder.
Step-by-step description
- Calls pass through and failures are counted.
- At the threshold the breaker trips and rejects calls.
- After the wait, one trial call tests recovery.
- The caller handles each rejected call.
- Closed counts recent failures and trips to open at the threshold.
- In the open state the breaker rejects every call immediately.
- Half-open lets a trial call through: success closes the breaker and failure reopens it.
- The caller handles the rejection.
The code below is a compact version. It takes the clock as an argument, so a test can move time by hand instead of sleeping.
import threading
import time
CLOSED, OPEN, HALF_OPEN = "closed", "open", "half_open"
class CircuitOpenError(Exception):
pass
class CircuitBreaker:
def __init__(self, max_failures=3, window=600.0, open_for=300.0,
clock=time.monotonic, is_failure=lambda e: True):
self._max_failures = max_failures
self._window = window # seconds a failure stays on record
self._open_for = open_for # seconds to stay open
self._clock = clock
self._is_failure = is_failure # which exceptions count as failures
self._lock = threading.Lock()
self._state = CLOSED
self._generation = 0 # bumped on every state change
self._failures = [] # timestamps of recent failures
self._opened_at = 0.0
self._trial_running = False
def call(self, fn, *args):
token = self._before_call() # may raise CircuitOpenError
try:
result = fn(*args) # the lock is NOT held here
except BaseException as exc: # bookkeeping only, then re-raise
if not isinstance(exc, Exception):
outcome = "neutral" # cancellation or exit: no verdict
elif self._is_failure(exc):
outcome = "fail"
else:
outcome = "ok" # the dependency answered
self._after_call(token, outcome)
raise
self._after_call(token, "ok")
return result
def _before_call(self):
with self._lock:
if self._state == OPEN:
if self._clock() - self._opened_at < self._open_for:
raise CircuitOpenError()
self._state = HALF_OPEN
self._generation += 1
self._trial_running = False
if self._state == HALF_OPEN:
if self._trial_running:
raise CircuitOpenError()
self._trial_running = True # check and mark in one step
return (self._generation, True)
return (self._generation, False)
def _after_call(self, token, outcome):
generation, is_trial = token
with self._lock:
if generation != self._generation:
return # state changed since the call began
now = self._clock()
if is_trial:
self._trial_running = False
if outcome == "ok":
self._state = CLOSED
self._generation += 1
self._failures.clear()
elif outcome == "fail":
self._trip(now)
return # neutral: still half-open, retry trial
if self._state == CLOSED and outcome == "fail":
self._failures.append(now)
self._failures = [t for t in self._failures
if now - t < self._window]
if len(self._failures) >= self._max_failures:
self._trip(now)
def _trip(self, now):
self._state = OPEN
self._generation += 1
self._opened_at = now
self._failures.clear()Read it in this order: call is the only public method; _before_call decides whether to allow the call and hands back a token; _after_call updates state from the result. The token holds a generation number, a counter that every state change increases, and a flag saying whether this call is the trial.
A call that finishes after the breaker has changed state since it started is ignored, which is a deliberate choice: a slow call that began while closed must not be mistaken for the half-open trial, or a second trial could start while the first is still running. The except BaseException branch only records the outcome and then re-raises, so a cancelled call releases the trial flag instead of leaving the breaker stuck in half-open. Because _trip stores the time of the trip and each call compares against it, no timer thread is needed.
The is_failure argument decides which exceptions count. By default every Exception does; pass, for example, lambda e: not isinstance(e, KeyError) so that a business error such as "not found" is treated as a healthy answer and does not trip the breaker.
Knowing it is half the job. Saying it is the other half.
Interview Clozo is an AI interview copilot for real interview rounds: it hears the question and puts a strong answer on your screen, and you say it in your own words. See the features or how it works.
Start Free Trial →Worked example
The scenario uses the parameters of a question that candidates report: a client calls a service, and three failures within ten minutes open the breaker for five minutes, after which it tries again. The call schedule is illustrative: one call per minute for 21 minutes, with the service down from minute 3 to minute 12 and healthy again from minute 13.1
Calls 0, 1 and 2 succeed. Calls at minutes 3, 4 and 5 fail, so at minute 5 the breaker holds three failures inside the ten-minute window and opens.1 The calls at minutes 6, 7, 8 and 9 are rejected without reaching the service. At minute 10 the five-minute wait is over, the breaker goes half-open and lets one trial through; the service is still down, so the trial fails and the breaker opens again.1 Calls at minutes 11, 12, 13 and 14 are rejected. At minute 15 the trial succeeds, the breaker closes and calls 16 to 20 succeed.1
| Quantity | Value (computed from illustrative inputs) |
|---|---|
| Calls made in the 21-minute run | 21 |
| Calls that reached the service | 13 |
| Calls rejected without reaching the service | 8 |
| Failing calls that reached the dead service, with the breaker | 4 |
| Failing calls it would have received with no breaker | 10 |
| Rejected calls while the service was healthy again | 2 |
| Minutes between recovery (minute 13) and the breaker closing (minute 15) | 2 |
The last two rows are the price of the pattern. The breaker is conservative, so it keeps rejecting for a little while after the service is back. Microsoft names the same tension: a breaker that stays open too long raises errors after the fault is fixed, and one that goes half-open too quickly can flap and slow callers.
Text description of the diagram
- The breaker opens at minute 5 after three failures and rejects eight calls in total.
- The trial at minute 10 fails and the trial at minute 15 succeeds.
Two more computed checks. Failures at minutes 0, 6 and 12 do not open the breaker, because by minute 12 the failure at minute 0 is outside the ten-minute window; three failures at minutes 0, 1 and 2 do.1 That is the sliding window doing its job. For the race, assume eight callers reach a half-open breaker together. If the check and the mark are separate steps and every caller does its check first, all 8 become trial calls; with the check and mark under one lock, exactly 1 does and 7 are rejected (a simulated worst-case interleaving, not a measurement of real threads).1
Trade-offs and edge cases
The breaker trades availability for protection. It rejects some calls that would have worked, to keep a struggling dependency alive and the caller's resources free. Microsoft says the pattern is not suited to protecting local, in-memory resources, where it only adds overhead, and not as a substitute for handling errors in business logic. It also lists message-driven systems, where failed messages usually go to a dead letter queue, and platforms whose load balancers or service mesh already handle recovery.
Design choices, with the usual position on each:
- What counts as a failure. Fowler says not all errors should trip the circuit; some are normal failures handled in regular logic. He also suggests different thresholds for different errors, such as a higher one for timeouts than for connection failures.
- Count or rate. A failure count is simple; a failure rate over a window, Fowler's example is 50%, behaves better when traffic varies a lot.
- Probe design. One trial call is simple. Microsoft's description closes the breaker after a specified number of consecutive successful trials, which is slower but more careful.
- Long timeouts. Microsoft warns that a breaker does not protect much if the dependency's timeout is very long, because threads stay blocked before a failure is even recorded.
- Hints from the dependency. An error response can carry enough information to trip at once, for example status code 429 or 503 with a retry time.
Text description of the diagram
- Retry assumes the fault is short-lived and calls again.
- A circuit breaker assumes the fault may last and stops calling.
- Retry logic runs through the breaker and stops when the breaker reports a lasting fault.
A retry is a policy that calls the same operation again, expecting a later attempt to succeed. Microsoft says the two patterns can be combined by retrying through a circuit breaker, as long as the retry logic stops when the breaker reports a fault that is not transient. For related state-and-time exercises, see event and log processing problems, the least recently used (LRU) cache in Python, caches with expiry times and time-based key-value stores and the CodeSignal in-memory database assessment.
How to answer it in an interview
Implement a circuit breaker and tell me how you would deploy it.
I would build a small state machine with closed, open and half-open states. Closed counts failures inside a time window, open rejects calls immediately for a fixed wait, and half-open lets one trial through: success closes it, failure reopens it. I would take the clock as a parameter for testing, and keep one breaker per dependency. The state change goes under a lock so only one thread probes, and I would never hold the lock during the remote call. In production I would log every state change and expose a manual reset.
The spoken structure: say the three states, say how each transition happens, then say what you did about threads and what the caller does on rejection. Before coding, ask which errors count as failures, whether time is a window or consecutive failures, and how many trials half-open allows.
Common mistakes
- "After three failures it is open forever." (wrong) Open is timed; the breaker needs a way back. (right) "It opens for a wait, then half-open tests recovery."
- "I'll just check the state and then set it." (wrong) Two threads can both pass the check. (right) "The check and the state change happen under one lock."
- "I hold the lock around the remote call for safety." (wrong) That serializes every caller behind a slow dependency. (right) "The lock covers bookkeeping only; the call runs outside it."
- "Any result that arrives in half-open is the trial's result." (wrong) A slow call from before the trip can finish later. (right) "Each call carries a generation token, and a stale one is ignored."
- "The breaker retries the call." (wrong) A breaker stops calls; retry is a separate policy. (right) "Retry sits outside the breaker and stops on an open-circuit error."
- "Rejecting is enough." (wrong) The caller still has a failed operation. (right) "The caller returns a cached value, queues the work or shows an error."
Follow-up questions
How would you deploy a circuit breaker in the real world?
Log every state change and raise an alert when a breaker opens, as Fowler recommends, and let operators trip or reset it by hand. Decide whether state lives per process or in a service mesh sidecar, which Microsoft says can provide circuit breaking without changing application code.
What is the difference between a circuit breaker and a retry?
A retry assumes the fault is short-lived and calls again; a circuit breaker assumes it may last and stops calling. They can be combined, with retries running through the breaker.
Which errors should trip a circuit breaker?
Errors that signal an unhealthy dependency, such as timeouts and connection failures. Ordinary business outcomes, such as "not found", should not count, and different error types can have different thresholds.
How do you choose the failure threshold and the wait time?
Match them to how the dependency usually recovers. Too long a wait rejects healthy calls; too short a wait makes the breaker flap between open and half-open. Start from observed outage lengths and adjust from the state-change logs.
Is a circuit breaker the same as a rate limiter?
No. A rate limiter caps how many calls a client may make, whatever the dependency's health; a circuit breaker stops calls because the dependency appears to be failing.
- A circuit breaker is a state machine: closed counts failures, open rejects calls, half-open tests recovery.
- Count failures in a sliding window so that occasional errors do not open the breaker.
- Check and mark the trial call under one lock, and never hold the lock during the remote call.
- Inject the clock so the open wait can be tested without sleeping.
- Keep one breaker per dependency, and give the caller a fallback for rejected calls.
Sources
- Circuit Breaker (bliki) — martinfowler.com (Martin Fowler), 6 Mar 2014
- Circuit Breaker pattern - Azure Architecture Center — Microsoft Learn, 26 Sep 2026
- Release It! Design and Deploy Production-Ready Software, 2nd edition — The Pragmatic Bookshelf (Michael T. Nygard), 18 Jan 2018