A circuit breaker wraps a call to a dependency — another service, a database, a third-party API — and stops sending requests to it once failures cross a threshold, instead of letting every caller keep retrying a service that is already struggling. The point is not to fix the failing dependency; it is to stop the failure from cascading, because a slow or down dependency that keeps receiving full traffic and timing out will exhaust threads, connections, and queues in every service that calls it, turning one outage into many. A circuit breaker fails fast instead, buying the struggling dependency room to recover.
How it works
A circuit breaker has three states. Closed is normal operation — requests pass through, and the breaker counts failures. Once failures cross a configured threshold (say, 50% of requests over the last 20 calls), the breaker trips to open, and every call fails immediately without touching the dependency at all, usually falling back to a cached value, a default response, or a clear error. After a cooldown period, the breaker moves to half-open, allowing a small number of test requests through. If those succeed, it closes again and resumes normal traffic; if they fail, it reopens and waits longer before trying again.
CLOSED --(failure threshold exceeded)--> OPEN
OPEN --(cooldown timer expires)--> HALF-OPEN
HALF-OPEN --(test calls succeed)--> CLOSED
HALF-OPEN --(test calls fail)--> OPEN
This is different from a plain retry or timeout. A timeout bounds how long one call waits. A retry tries again after a failure. Neither one stops a caller from continuing to hammer a dependency that is already failing under load — a circuit breaker is specifically the mechanism that says "stop calling this for a while."
Circuit breakers, retries, and timeouts compared
| Mechanism |
What it does |
What it does not do |
| Timeout |
Bounds how long a single call waits |
Nothing to prevent repeated calls to a failing dependency |
| Retry |
Re-attempts a failed call, often with backoff |
Can amplify load on an already-struggling dependency if unbounded |
| Circuit breaker |
Stops calls entirely once failures cross a threshold |
Does not fix the underlying failure — it isolates it |
| Bulkhead |
Limits concurrent calls or isolates resource pools per dependency |
Does not detect failure rates on its own |
In production, these are combined, not chosen between: a bounded retry with backoff, wrapped in a circuit breaker, inside a bulkhead that caps concurrent calls, is the standard resilience stack for a call to an unreliable dependency. Resilience4j (Java), Polly (.NET), and cockatiel (Node) are the common library-level implementations. Service meshes like Istio and Linkerd implement circuit breaking at the network layer via their sidecar proxies, which means every service gets the behavior without each team hand-rolling it. Netflix's original Hystrix library popularized the pattern but is now in maintenance mode; new projects should reach for Resilience4j or mesh-level breakers instead.
Common mistakes
- No fallback behind the open state. Tripping the breaker without deciding what happens next — a cached response, a default value, a clear user-facing error — just moves the failure from slow to abrupt without actually handling it.
- Thresholds tuned by guesswork. A threshold set too sensitive trips on normal traffic blips; one set too loose lets a real outage cascade before it opens. Base thresholds on observed failure-rate baselines, not a default left untouched.
- Never validating half-open behavior. Teams test that the breaker opens under failure but rarely verify it actually recovers cleanly — a half-open state that lets too many test calls through can re-trip immediately and get stuck flapping.
- Applying the breaker at the wrong granularity. Wrapping an entire service behind one breaker means one flaky endpoint trips protection for all of it. Scope breakers per dependency, ideally per downstream call type.
FAQ
Is a circuit breaker the same as rate limiting?
No. Rate limiting caps how much traffic a caller sends regardless of success or failure. A circuit breaker reacts to observed failures on a specific dependency and stops calling it until it recovers.
Does a circuit breaker fix the failing service?
No — it protects callers from a failing dependency and gives that dependency room to recover by reducing load, but the underlying problem still needs to be fixed separately.
Should every service call be wrapped in a circuit breaker?
Calls to external dependencies, other services, and anything over a network, yes. In-process function calls do not need one — there is no partial failure mode to protect against.
How do I choose failure thresholds?
Start from observed baseline error rates during normal operation, then set the threshold meaningfully above that baseline so normal noise does not trip the breaker, and validate the choice with real failure injection rather than picking a number from documentation.
Where to go next
Circuit breakers are one piece of a resilience strategy — pair them with the saga pattern for multi-step operations that need compensation logic when a downstream call fails partway through. To validate that your breaker thresholds and fallbacks actually work under real failure, see the chaos engineering guide for 2026. And if you are deciding whether to implement breakers per-service or centrally, service mesh explained covers what a mesh gives you out of the box.