A retry loop makes a struggling service worse. A circuit breaker is the piece of code that decides to stop calling — and knows how to start again.
When a downstream dependency gets slow or starts erroring, the naive caller keeps sending requests and keeps waiting on timeouts. Each waiting request holds a thread, a connection, a chunk of memory. The dependency's problem becomes your outage — that is a cascading failure, and it is how one bad database takes down five healthy services.
A circuit breaker wraps the outbound call in a small state machine that watches the failure rate. Past a threshold it trips open and every subsequent call fails immediately, without touching the network. Two things are bought at once: the caller stops burning resources on calls that were going to fail, and the dependency gets an unloaded window in which to actually recover.
The subtle part is not opening. It is closing. After a cooldown the breaker allows a few trial requests through — half-open — and lets reality decide. Success closes it, failure re-opens it. Recovery is measured, never assumed.
Requests flow through. Outcomes are recorded in a rolling window; failures and timeouts are counted.
No call is attempted. The breaker returns an error or a fallback in microseconds. The dependency sees zero load from you.
A small, capped number of trial requests are let through. Everything else still fails fast while the probe runs.
A bounded, recent view of outcomes — sliding count or sliding time window. Recent-only is the point: yesterday's failures must not trip today's breaker, and a lifetime average never trips at all.
A rate plus a minimum volume. 50% of ≥20 calls is sane; 50% of 2 calls trips on a coin flip. Without the volume floor, low-traffic endpoints flap constantly.
How long to stay open, then how many trial calls to admit. Often exponential: each failed probe lengthens the next wait, so a dependency that is genuinely down is not hammered every few seconds.
What the caller returns while open — cached value, sane default, degraded feature, queued write, or an honest error. A breaker with no fallback converts a slow failure into a fast one; useful, but only half the work.
Trip percentage — commonly 50%. Lower is twitchier, higher tolerates more damage.
Samples required before any trip. Guards low-traffic endpoints from statistical noise.
Cooldown before probing — seconds, not minutes, and ideally backing off on repeat failures.
Concurrent probes allowed. Small: 1–5. This is your controlled experiment, not your traffic.
A retry policy of 3 attempts sitting inside the breaker triples the load you send to a struggling dependency and triples the time each thread is held — while reporting only one outcome to the window. Put the retry outside, or bound total attempts, and always count timeouts as failures.
Second trap: no observability. A breaker that trips silently turns a loud outage into a mysterious one. Emit state transitions as events and alert on them — "breaker opened" is one of the highest-signal alerts a distributed system can produce.