Circuit breakers and retries with jitter
Stop calling something that is already failing, and never retry all at the same moment.
What problem does circuit breakers solve?
When a dependency gets slow, every caller piles up waiting on it, holding threads and connections. Your service dies of someone else's outage. That is a cascading failure, and it is how one small problem can take down a whole platform.
Retries make it worse. The dependency is struggling, so everyone retries, so it receives more traffic than before and cannot recover. The fix is to fail fast when things are bad, and to retry in a way that does not line everyone up together.
What circuit breakers costs you
Every concept in here states a cost. A technique with no price is a technique you have not understood yet — and naming the cost out loud is most of what separates a senior answer from a junior one.
When to reach for circuit breakers, and when not to
Use it when
- Every call to another service or an external API. Timeouts and breakers are defaults, not features.
- A dependency that is optional, like recommendations or personalisation, where a fallback is better than an error.
- Anywhere you have seen threads pile up on a slow call.
Do not, when
- A single local database call, where failure means you cannot answer at all. A breaker adds little there. A timeout still matters.
- Operations that are not safe to repeat and carry no idempotency key. Blindly retrying a payment is worse than failing.
How circuit breakers actually works
Start with this: put a timeout on every remote call. Without one, a dependency that hangs holds your resources forever, and no amount of clever breaker logic saves you. Set the timeout from the real latency distribution, a bit above p99, not a round number someone guessed.
The breaker states. Closed means calls flow while failures are counted. Once the failure rate crosses a threshold over a meaningful window, it opens, and calls fail immediately without waiting. You stop spending resources on something that will not answer. After a cooldown it goes half-open and allows one trial call. Success closes it. Failure opens it again. Use a failure rate over a window rather than a raw count, or a quiet endpoint takes an hour to trip and a busy one trips on a hiccup.
Retries, done properly. Only retry things that are safe to repeat, or that carry an idempotency key. Only retry errors that might be temporary, like a timeout or a 503, never a 400. Cap the attempts at two or three. Use growing delays between attempts, and add randomness so callers do not line up. Without that randomness, a thousand clients that all failed at the same moment all retry at the same moment, and the recovering service is knocked over by the wave. The randomness is one line of code, and it can decide whether the service recovers or suffers a second outage.
One refinement worth mentioning is a retry budget: the whole client is allowed to spend only a small percentage of its traffic on retries. That caps the amplification no matter how many individual calls decide to retry, which matters most in a deep chain of calls. Three layers that each make three attempts turn one user request into 27 at the bottom.
Bulkheads are the companion idea. Give each dependency its own limited pool of connections or threads, so a slow one uses up only its own share and the rest of your service keeps working. Without that, one slow dependency eats every thread and takes down endpoints that never called it.
Then say what happens when the breaker is open, because that is what decides the user experience. Serve old cached data, fall back to a simpler response, drop an optional feature, or return a clear error. Decide this per dependency in advance, or the default is a blank page.
- Retrying without idempotency and creating duplicate side effects.
- Growing delays with no randomness, producing one synchronised retry wave.
- A breaker with no defined fallback. You fail fast into a blank page.
- No timeout at all, which makes everything else here irrelevant.
The circuit breakers follow-up an interviewer will ask
"Your recommendation service is down. What does the user see?"
The page, without recommendations. And that has to be a decision you made on purpose, before the incident, not something the code stumbles into. In practice: the call has a tight timeout, maybe 100 ms, because recommendations are not worth delaying the page for. A breaker opens after a burst of failures, so we stop waiting at all. And the fallback is a cached or generic list, or the section simply is not shown. The failure to avoid is the page that waits three seconds for something it did not need and then errors, so an optional feature caused a total outage. I would also give it its own connection pool, so even while it is timing out it cannot eat the capacity the checkout path needs. The cost of all this is that recommendations quietly get worse during an incident and users are not told. That is the right trade here, but it means I need an alert, or nobody notices for a week.
Your turn
Why add jitter to retry backoff? Describe the exact failure it prevents.
Learn it from the source
This page gets you interview-ready. These take you to where the idea is actually specified or measured — read them when you want the real depth.
- Martin Fowler — CircuitBreaker ↗The pattern as originally written up, with the state machine drawn out.
- Timeouts, retries and backoff with jitter ↗Why retries without jitter turn a blip into an outage.