Why circuit breakers exist: stop hammering a failing dependency
coreintermediateA remote call can fail outright or hang until a timeout is reached — either way, every caller that keeps trying it wastes resources (threads, connections, time) on a call that is unlikely to succeed. A circuit breaker wraps a call to a dependency and, after enough recent failures, stops attempting the call at all for a while — failing fast locally instead of repeatedly waiting on a call that keeps failing. This protects the caller from wasting its own resources, and protects the struggling dependency from a continuous stream of load that makes recovery harder, not easier.
Think of it as
It is named after the electrical circuit breaker in a building for a reason. When a circuit is overloaded, the breaker trips and cuts power immediately, rather than letting every appliance keep drawing current into a fault and risking a fire. A software circuit breaker does the same thing to a failing dependency: once it detects a fault, it "trips" and stops sending more calls through, protecting the caller from wasting effort and giving the dependency room to actually recover, rather than being hit by an unrelenting stream of retries the moment it shows the first sign of trouble.
What we're doing: Show the resource-exhaustion cascade a missing circuit breaker allows, and how a breaker prevents it.
- 8
- This is the moment the cascade begins — A's entire worker pool is consumed by calls to one slow dependency.
- 13
- The failure has now spread past its origin: A is unavailable for ALL requests, including ones that never touch B, purely because of thread exhaustion.
Why this works: This is the exact failure a circuit breaker on the call to B prevents — once B's failure rate crosses the trip threshold, A stops calling B at all, freeing every thread that would otherwise be stuck waiting, and A stays available for everything that does not depend on B.
Assuming a timeout alone prevents the cascading-failure scenario a circuit breaker exists for
Wrong
Better
What you see: A service with a "reasonable" per-call timeout still experiences a full outage under sustained load from one failing dependency — the timeout bounds each individual call, but does nothing to stop every new incoming request from independently paying that same bounded cost, which at high enough request volume still exhausts the caller's capacity.
Why: A timeout answers "how long should I wait for one call," while a circuit breaker answers a different question entirely — "should I even attempt this call at all, given how the last N attempts to this same dependency went" — and only the second question actually stops a caller from repeatedly paying the timeout cost for calls it already has strong evidence will fail.
- Without a breaker
- Full timeout paid on every single attempt
- Continuous load on the failing dependency
- A thread/connection held per in-flight call
- With a breaker (tripped)
- Immediate failure — no real call attempted
- Zero load while the breaker is open
- Caller resources freed immediately
With vs without a circuit breaker, same failing dependency
Remember: A circuit breaker stops a caller from repeatedly attempting a call to a dependency that recent evidence shows is failing — failing fast instead of failing slow, which protects both the caller's own resources and gives the struggling dependency room to actually recover.
See also: state machine · bulkhead isolation · retry amplification

