Interview Prep Zoneby CuriouserLabs

Question 5 of 5 · senior level

A downstream service slows down and your whole platform goes with it. What went wrong, and how do you stop it?

🎤 Say this first

That's a cascading failure, and retries are usually the accelerant. A slow dependency makes callers hold threads/connections longer, their pools exhaust, their callers time out and retry, which multiplies load on the already-struggling service — a retry storm where each layer amplifies the one below (3 retries × 3 layers = 27× load). The countermeasures, in order of impact: timeouts everywhere (an unbounded wait is the root cause — a request that will be abandoned should stop consuming resources), bounded retries with exponential backoff and jitter (never retry non-idempotent or non-retryable errors), circuit breakers so a failing dependency fails fast instead of consuming your threads, bulkheads so one dependency can't drain the whole pool, and load shedding at the edge — dropping 10% of requests deliberately beats losing 100% to collapse.

The full picture

  • Retry amplification is multiplicative across layers — say the arithmetic out loud: if every layer retries 3×, a three-layer call chain turns 1 user request into up to 27 downstream calls at exactly the moment the downstream is least able to serve them. The fix is to retry at one layer only (usually the outermost that can still be useful) and to make the others fail fast.
  • Jitter is not a nicety, it's the fix: synchronized exponential backoff makes every client retry at the same instants, producing traffic spikes that re-break a recovering service. Full jitter (sleep = random(0, base·2^n)) spreads them out — AWS's architecture blog on this is the standard reference and it's worth naming.
  • Timeout budgets must shrink down the chain: if the user-facing request has a 2s budget, a service 3 hops deep cannot have a 5s timeout — it will be working on a request nobody is waiting for. Propagate a deadline (gRPC does this natively) rather than configuring independent timeouts that don't add up.
  • Circuit breakers convert a slow failure into a fast one, which is the actual goal — threads freed, pools healthy, a fallback served. Understand the states (closed → open after a failure threshold → half-open probe) and the parameter that matters most: how many probes you allow in half-open, since a stampede of probes re-kills a recovering service.
  • Bulkheads and load shedding are the underrated pair: separate connection pools per dependency mean a slow recommendations service can't starve checkout; and shedding low-priority traffic at the edge (with a 429 and a Retry-After) protects the core. Graceful degradation is a design output, not an accident.
PatternPreventsWatch out
Timeout + deadline propagationunbounded resource holdbudgets must shrink per hop
Backoff + full jittersynchronized retry spikescap total attempts and elapsed time
Circuit breakerslow failure consuming threadshalf-open probe stampede
Bulkhead (pool per dependency)one dependency draining all threadsmore pools to size and monitor
Load shedding / rate limittotal collapse under overloadmust shed the right traffic
Idempotencyduplicates created by retriesrequires stable keys — see delivery semantics

🔄 Likely follow-up questions

  • Why does full jitter beat plain exponential backoff for a recovering service?
  • Where in a three-hop call chain should retries live, and why only there?
  • How do you choose which traffic to shed when you must shed some?