SysDesignPrep.com
Interactive concepts

Circuit breaker pattern: an interactive simulator

A circuit breaker wraps calls to another service and stops making them when that service is failing. While calls succeed it is closed and lets them through. When too many recent calls fail, it opensand fails every call at once, without waiting on a service that will not answer. After a cool-down it goes half-open and lets one trial call through: if it succeeds the breaker closes, if not it opens again.

The playground makes the same minute of calls twice, without and with a breaker, while the service has an outage. Compare how many calls hit the dead service and how much time the client spends waiting on it.

Timeout

The service stops answering at 15 s. Without a breaker, every call waits the full 1000 ms timeout and then fails: 400 calls and 400 s of client time spent waiting. With one, the breaker opens at 16.4 s, once half its recent calls have failed, and from then on calls fail at once: only 31 reach the dead service, mostly the half-open trials. The price: the breaker closes 4.5 s after the service recovers, when a half-open trial finally succeeds.

Without a breaker
With a breaker
0 sincident 15 to 35 s60 s
withoutwith
Calls that reached the service during the incident40031
Client time spent waiting on it400 s31 s
Calls failed at once by the breaker0458
succeededfailed after waiting (bar: the wait)failed at once, breaker openclosedopenhalf-open

How a circuit breaker works

  1. Closed: calls go through, and the breaker records whether each succeeded. A call that times out counts as a failure.
  2. Trip: when the failure rate over a window of recent calls passes a threshold (say 50% of the last 20, once there are at least 10), it opens.
  3. Open: every call fails immediately with an error or a fallback. Nothing is sent to the failing service.
  4. Half-open: after the open period, one trial call (or a few) is let through.
  5. Reset: if the trial succeeds the breaker closes and normal traffic resumes; if it fails, the breaker opens for another period.

Why a circuit breaker helps

Without one, a dead dependency fails slowly: every call waits for the full timeout. In the playground’s outage, 400 calls each wait a second, which in a real server is 400 seconds of threads or connections held for nothing, and enough of that takes the caller down too: a cascading failure. With a breaker, the waiting stops after the first second or two, the caller answers at once with a fallback, and the struggling service gets fewer requests while it recovers instead of a pile-up of retries.

The cost is the open period: after the service recovers, the breaker keeps failing calls until its next trial succeeds. A shorter open period recovers sooner but probes a sick service more often.

Circuit breaker vs retry

Retries handle a blip: one call fails, the next probably succeeds. A circuit breaker handles an outage, where the next call will fail too and retrying only adds load. They work together: retry a failed call once or twice with backoff, and put the breaker outside the retries so that a dependency that keeps failing stops being called at all. Retries without a breaker multiply traffic to a service that is already down.

Choosing the settings

SettingTypical valueToo lowToo high
TimeoutA little over the dependency’s p99Healthy slow calls count as failuresEvery failure holds a thread that long
Failure threshold50% over the last 20 to 100 callsTrips on normal noiseKeeps calling a service that is mostly down
Minimum calls10 or moreTwo early failures trip itSlow to notice on low traffic
Open period5 to 60 sProbes a sick service constantlyStays open long after recovery

Keep one breaker per dependency (and per host when hosts fail independently), so one bad payment provider does not trip calls to the search service. Libraries do this for you: Resilience4j and Polly implement this state machine with a sliding window and a failure rate threshold; Netflix’s Hystrix popularised the pattern and is now in maintenance. In a service mesh, Envoy’s outlier detection ejects failing hosts, and what it calls circuit breakers are limits on connections and pending requests.

A circuit breaker in code

import time

class CircuitBreaker:
    def __init__(self, threshold=0.5, window=20, min_calls=10, open_for=5.0):
        self.threshold, self.window, self.min_calls, self.open_for = threshold, window, min_calls, open_for
        self.results, self.state, self.opened_at = [], "closed", 0.0

    def call(self, fn, fallback):
        if self.state == "open":
            if time.monotonic() - self.opened_at < self.open_for:
                return fallback()                     # fail fast, without calling
            self.state = "half-open"                  # time's up: let a trial through
        try:
            result = fn()                             # fn must have its own timeout
        except Exception:
            self._record(False)
            return fallback()
        self._record(True)
        return result

    def _record(self, ok):
        if self.state == "half-open":                 # the trial decides
            self.state = "closed" if ok else "open"
            self.opened_at, self.results = time.monotonic(), []
            return
        self.results = (self.results + [ok])[-self.window:]
        failed = self.results.count(False)
        if len(self.results) >= self.min_calls and failed / len(self.results) >= self.threshold:
            self.state, self.opened_at = "open", time.monotonic()

This sketch is single-threaded. A real one also lets only one trial through while half-open, counts slow calls as failures, and exposes its state as a metric, because a breaker opening is something to alert on.

In a system design interview

Say what it wraps, the numbers and the fallback: “Calls to the payment provider go through a circuit breaker with a 2 second timeout that trips at 50% failures over the last 50 calls and stays open 30 seconds; while it is open we accept the order, queue the charge and tell the customer it is processing.” Expect the follow-ups: the fallback (cached data, a default, a degraded page, or a queue), how retries and the breaker interact, bulkheads that cap how many threads one dependency can hold, and how you know it opened.

Read the resilience guide for retries, timeouts and bulkheads, or watch a strong candidate design a payment system.

Questions people ask

What is the circuit breaker pattern?

A wrapper around calls to another service that stops making them when the service is failing. When the failure rate of recent calls passes a threshold it opens and fails calls immediately instead of waiting on timeouts; after a cool-down it lets a trial call through and closes again if that succeeds.

What are the three states of a circuit breaker?

Closed, where calls go through and failures are counted; open, where calls fail at once without reaching the service; and half-open, where after the open period a trial call is let through to test whether the service has recovered. A successful trial closes the breaker and a failed one opens it again.

What is the difference between a circuit breaker and a retry?

A retry repeats one failed call, which fixes a brief blip. A circuit breaker stops calling a service that keeps failing, which protects both sides during an outage. Use both: a retry or two with backoff for individual failures, and a breaker around them so a dead dependency stops being called and retries do not pile on.

How do you choose circuit breaker thresholds?

Set the timeout a little above the dependency’s p99 latency, trip at around 50% failures over the last 20 to 100 calls with a minimum of 10 calls so noise does not trip it, and stay open 5 to 60 seconds. Shorter open periods recover sooner but probe a sick service more often.

What should happen when a circuit breaker is open?

The caller should answer at once with a fallback rather than an error where it can: cached or stale data, a sensible default, a degraded feature, or accepting the work into a queue to finish later. The open state should also be visible as a metric and an alert, since it means a dependency is down.

What is the half-open state in a circuit breaker?

The state after the open period ends, in which the breaker lets one trial call, or a few, reach the service. If the trial succeeds the breaker closes and normal traffic resumes; if it fails the breaker opens for another period. It lets the caller notice recovery without sending full traffic to a service that may still be down.

What is the difference between a circuit breaker and a rate limiter?

A rate limiter protects a service from too many requests by rejecting the excess, whatever the service’s health. A circuit breaker protects a caller, and the service it calls, from a dependency that is failing, by stopping calls based on how they fail. A rate limiter looks at volume; a breaker looks at errors and timeouts.