The Cascading Failure Problem
Distributed systems are built from many services that call each other over the network, and networks are unreliable. When one downstream service starts responding slowly or not at all, every upstream caller waiting on that response is also affected. If nothing intervenes, those callers hold open connections, occupy threads, and consume memory while they wait for a response that may never come. As more requests arrive, the pool of available threads and connections in the calling service shrinks until it too becomes unresponsive. This is how a single failing dependency in a large architecture can trigger a chain reaction that takes down services with no direct relationship to the original problem. A checkout service calling a slow inventory service can end up starving the entire e-commerce platform, even though the payment and shipping systems were never at fault. The circuit breaker pattern exists specifically to interrupt this chain before it spreads, acting as a protective boundary around every risky network call.
Closed: Business as Usual
In the Closed state, a circuit breaker behaves as if it were not there at all. Requests pass straight through to the downstream service, and the breaker's only job is to watch quietly in the background. Every time a call succeeds or fails, the breaker records the outcome, typically maintaining a rolling window of recent results or a running failure count. This is the default, healthy state, and most requests in a well-functioning system spend their entire lifetime here. The breaker is not blocking anything or adding meaningful latency; it is simply keeping score. What makes this state important is the threshold logic layered on top of it: the breaker defines a failure rate or a consecutive-failure count that, once crossed, signals that the downstream dependency is no longer healthy enough to trust. As long as failures stay below that threshold, occasional errors are treated as normal noise rather than a systemic problem, and the breaker stays Closed, letting traffic flow uninterrupted while continuing to monitor for a genuine deterioration in service health.
Open: Failing Fast on Purpose
Once failures cross the configured threshold, the breaker trips into the Open state, and its behavior changes completely. Instead of forwarding requests to the struggling service, the breaker immediately rejects them, often returning a fallback response, a cached value, or a clear error, without even attempting the network call. This is the heart of the pattern's value: failing fast is deliberately better than failing slow. A request that times out after thirty seconds still consumes a thread, a connection, and memory for that entire thirty seconds, and if thousands of requests are queued up the same way, the calling service's own resources get exhausted, turning it into a second casualty of the original failure. By rejecting requests instantly, the Open breaker keeps the caller's thread pools and connection pools free, preserves its ability to serve other traffic, and prevents the failure from propagating upward through the call chain. It also, just as importantly, stops hammering an already struggling downstream service with more traffic than it can handle, giving that service the breathing room it needs to recover instead of being kept underwater by continuous retries.
Half-Open: Testing the Waters
A breaker cannot stay Open forever, because the underlying service might have already recovered while the breaker keeps rejecting everything blindly. After a configured timeout period elapses, the breaker transitions to the Half-Open state, a cautious middle ground between total distrust and full confidence. In this state, the breaker allows a small number of trial requests to pass through to the downstream service while continuing to block the rest. The outcome of these trial requests determines the next transition: if they succeed, the breaker concludes the dependency has recovered and moves back to Closed, resuming normal traffic flow and resetting its failure counters. If even one trial request fails, the breaker assumes the problem persists, immediately snaps back to Open, and restarts the timeout clock before trying again. This probe-and-decide cycle prevents two opposite mistakes: reopening the floodgates too early onto a service that has not actually recovered, and staying needlessly closed off after the problem has already resolved.
Circuit Breakers in Real-World Microservice Architectures
The circuit breaker pattern moved from theory to mainstream practice largely thanks to Netflix, whose Hystrix library popularized it for Java-based microservice fleets handling enormous request volumes across hundreds of interdependent services. Hystrix wrapped calls to dependencies with configurable thresholds, fallbacks, and timeouts, and its dashboard made it possible to watch circuits trip and recover across an entire fleet in real time. Although Netflix has since moved Hystrix into maintenance mode, its ideas live on directly in resilience4j, a lightweight, modular library that implements the same core patterns, circuit breakers, rate limiters, bulkheads, and retries, with a design suited to modern, reactive Java applications. Similar implementations exist across virtually every ecosystem, from Polly in .NET to opossum in Node.js, and API gateways and service meshes increasingly bake circuit breaking in as infrastructure-level behavior rather than application code. In every case, the goal is the same: keep one broken dependency from crashing everything that depends on it, turning a potential platform-wide outage into a contained, recoverable incident affecting only the directly impacted feature.
Frequently asked questions
How is a circuit breaker different from a simple retry mechanism?
A retry mechanism keeps attempting the same failing call, which can actually make things worse by adding load to an already struggling service. A circuit breaker instead stops attempting calls altogether once failures cross a threshold, giving the downstream service room to recover rather than piling more requests onto it.
What happens to requests while the breaker is Open?
Requests are rejected immediately without ever reaching the downstream service, usually triggering a fallback response, a cached result, or a graceful error message. This avoids the long waits and resource exhaustion that come from letting requests time out slowly.
How is the failure threshold usually decided?
Most implementations use either a consecutive-failure count or a failure rate calculated over a rolling window of recent requests, such as tripping the breaker if more than fifty percent of the last twenty calls failed. The right threshold depends on the service's normal error rate and how tolerant the system needs to be of transient blips.
Why not just let requests time out naturally instead of using a circuit breaker?
Slow timeouts still consume threads, connections, and memory in the calling service for the full duration of the wait. If many requests are stuck this way simultaneously, the caller's own resources can become exhausted, spreading the failure to services that were never actually broken.
Is the circuit breaker pattern only useful for microservices?
It is most associated with microservices because of how many network calls flow between services, but the same logic applies to any client calling an unreliable dependency, including database connections, third-party APIs, and even calls between a monolith and external systems.
Try it live
Everything above runs in your browser — open The Circuit Breaker Pattern: Stopping Cascading Failures in Software Systems and change the parameters while it is running. Nothing is installed, nothing is uploaded, the whole model lives in one tab.
▶ Open The Circuit Breaker Pattern: Stopping Cascading Failures in Software Systems simulation