Skip to main content

Circuit Breaker

When a downstream service starts failing, the naive behavior — every caller keeps retrying every request against it — makes things worse, not better: a struggling service gets hit with the same load (or more, from retries) while it's least able to handle it, and every caller wastes time and resources waiting on calls that are very likely to fail anyway. A circuit breaker is the pattern that stops this: it wraps calls to a dependency, watches for failures, and once failures cross a threshold, it stops sending traffic to that dependency entirely for a while — failing fast instead of piling on.

The three states, named after the electrical original​

The pattern borrows its name and its state machine directly from an electrical circuit breaker, and the analogy holds up well:

System Design Lab
  • Closed — the normal state. Calls flow through to the dependency, and the breaker tracks the failure rate.
  • Open — once failures exceed a threshold, the breaker "trips": for a cooldown period, it fails every call immediately, without even attempting to contact the struggling dependency. This is the core value — protecting both the caller (no more wasted time waiting on a call likely to fail) and the callee (no more load added on top of whatever's already wrong).
  • Half-open — after the cooldown, the breaker cautiously lets a small number of test calls through. If they succeed, it closes again and resumes normal traffic; if they still fail, it reopens and waits another cooldown period before trying again.

Failing fast is the point​

Without a breaker, a caller waiting on a slow, failing dependency ties up its own resources (connections, threads, request-handling capacity) for however long that call takes to time out — and if enough callers pile up waiting the same way, the caller can fail too, purely from resource exhaustion, even though its own logic was fine. This is exactly the cascading-failure scenario a circuit breaker exists to interrupt: by failing immediately once the breaker is open, a caller gets an instant, predictable failure instead of a slow, resource-consuming one, and can fall back to a default response, a cached value, or a graceful error far faster than waiting out a real timeout.

Where it lives in a real system​

A circuit breaker naturally wraps any single outbound call to a dependency — a service-to-service call, a database query, a call to a third-party API. It's exactly the resilience pattern named as a natural fit for an API Gateway to own, since every downstream call in a system already tends to flow through the gateway anyway, making it a convenient single place to track failure rates and trip per-dependency without instrumenting every individual service that makes the call.

Circuit breaker vs. rate limiting: reacting vs. preventing​

It's worth being precise about how this differs from Rate Limiting, since both are protective patterns that reject requests under stress: rate limiting protects a service from being overwhelmed by too much legitimate demand, rejecting requests before they'd exceed a known-safe capacity — it's proactive, and doesn't require anything to actually be broken yet. A circuit breaker instead protects callers from a dependency that's already failing, reacting to observed failures rather than anticipating load. Many real systems use both together: rate limiting on the way in to a service, circuit breakers on every outbound call it makes to its own dependencies.

Why this matters in an interview​

Any design involving a call to another service or a third-party dependency benefits from naming a circuit breaker as the answer to "what happens if that dependency is slow or down" — it's a concrete, well-known pattern that shows you're thinking about cascading failure, not just the happy path. Naming the three states and the half-open recovery step specifically (rather than just "we'll add a circuit breaker") signals you understand the mechanism, not just the vocabulary.

Circuit breaker vs. no protection on outbound calls: pros and cons​

Pros

  • Fails fast instead of piling up slow, resource-consuming calls to a struggling dependency
  • Reduces load on an already-failing dependency instead of adding to it
  • Recovers automatically via the half-open state once the dependency is healthy again

Cons

  • Adds a stateful component (failure counts, timers) to every protected call path
  • Requires tuning failure thresholds and cooldown periods to the specific dependency
  • A misconfigured breaker can trip on transient blips, rejecting calls unnecessarily

Further Reading​

Saved locally in your browser — visible in the sidebar as you go.