BitBootcampBitBootcamp ← all explainers 🔄 Review ⚙ Settings

Pattern — Circuit Breaker

The problem it solves

A dependency you don't control goes slow or down, and your callers keep marching straight into it — parking threads on a dead-slow vendor until the pool starves (Drill 76 — The Timeout That Wasn't Set), or retrying a stalled service in lockstep until the retries themselves become the load (Drill 80 — The Retry Storm). In both, the fault is long over but the caller keeps hammering, so a momentary blip latches into a sustained outage. The missing primitive is something that notices the dependency is unhealthy and stops calling it.

The pattern

A circuit breaker is a stateful proxy around one downstream that counts failures and short-circuits calls when they cross a threshold. It has three states: closed (calls flow, failures counted), open (calls fail instantly without touching the dependency — for a fixed cooldown), and half-open (after the cooldown, a single probe is allowed; success closes the breaker, failure re-opens it). Timeouts and errors both count as failures. The point is to convert "wait 30 seconds then fail" into "fail in one millisecond," so a sick dependency degrades one feature instead of consuming the caller.

How to apply it

Wrap each downstream in its own breaker instance (never share one across dependencies) and configure four numbers:

The breaker is one guardrail in a set: put the dependency behind a bulkhead (its own bounded pool) so its breaker's blast radius is capped, and if you retry, retry inside the breaker with jittered backoff and a retry budget so a re-opened breaker gets zero retries, not three.

When to use it — and when not

Use it for calls to something you don't control on a hot path — an external vendor, an internal service on the request path, anything where "slow" or "down" can starve the caller. It is the wrong tool when there is no fallback and the call is mandatory (a payment authorization that must complete can't fail-fast into a lie — there you need queueing and idempotent retry, not a breaker), for a single non-critical background job (a breaker's bookkeeping isn't worth it), or when the real defect is a persistently overloaded dependency — a breaker masks the symptom and buys time, it does not add capacity. A breaker set too tight (low window, low threshold) becomes its own outage, flapping open on normal variance.

Signals it's working

Seen in the wild

Netflix Hystrix popularized the pattern (breaker + timeout + bulkhead in one library); Resilience4j and Polly (.NET) are the current standard implementations; Envoy and Istio ship outlier-detection circuit breaking at the service-mesh layer; AWS and Google SRE post-mortems of retry-storm / metastable failures prescribe exactly this — a breaker so a dead dependency gets zero retries — as the fix.

See it

CompareScene carries the point: without the breaker, caller p99 climbs to 30s and the pool starves; with it, the breaker trips at 50% errors and p99 pins at the 5s timeout — same outage, one feature degraded instead of the whole service.

Named concepts: circuit breaker, fail-fast, half-open probe.