A single client retrying a single failed request looks purely defensive — something didn’t work, ask again, no harm done. The picture changes entirely once there are many clients, because a retry isn’t just a second attempt from one caller’s point of view, it’s additional load from the receiving system’s point of view, and the two perspectives can point in opposite directions at exactly the moment it matters most. The situation most likely to make a request fail — the downstream system is overloaded or degraded — is the same situation in which a wave of retries does the most damage, because it adds demand precisely when capacity is already the scarce thing.

This produces a specific, well-known failure pattern: a service degrades slightly, requests start timing out, every client with a retry policy fires a second attempt at roughly the same moment, the extra load pushes the service further into overload, more requests fail, more retries fire, and the system that might have recovered on its own gets held underwater by the very mechanism that was supposed to add resilience. The retries weren’t wrong individually — each one was a reasonable response to an isolated failure. The problem is that “reasonable individually” doesn’t add up to “reasonable in aggregate” when thousands of callers make the same reasonable decision in near-lockstep, which is exactly what happens when they’re all reacting to the same underlying event.

Two features of naive retry logic make this worse than it needs to be. The first is synchronization: if every client retries after the same fixed delay, their retries arrive together, recreating a spike instead of smoothing it out — the load looks less like a steady stream and more like a series of aftershocks, each roughly as sharp as the first. The second is unboundedness: a policy that keeps retrying without a cap can turn one failed request into an indefinite stream of attempts, which is fine when the failure was transient and costly when it wasn’t, because now a system that was already struggling has one more caller hammering it forever.

The standard fixes exist because they target these two specific properties. Backoff — waiting longer between each successive attempt — buys the downstream system recovery time instead of demanding it be ready again on the original schedule. Jitter — randomizing the delay rather than using a fixed one — breaks the synchronization, so a thousand clients failing at once don’t retry at once; they spread out into something the receiving system can actually absorb. A retry budget or circuit breaker addresses the unboundedness directly, giving up after a threshold instead of retrying forever, which converts an indefinite drag on a struggling system into a bounded one and lets the caller fail informatively instead of quietly compounding the problem.

The throughline connects back to the rest of this thread: a retry, like a cache or an assumption about scale, looks like a purely local decision from where you’re standing but has consequences that only show up in aggregate, at the system level, often in exactly the conditions where those consequences are worst. The individual retry is invisible in cost the same way the individual cached value or the individual quadratic loop was invisible — fine in isolation, expensive in combination, and expensive specifically under load, which is the one condition every other piece of this thread has kept pointing back to as where hidden costs stop being hidden. Retry with backoff, jitter, and a limit — not because any single retry is dangerous, but because a system’s worth of them, arriving together, is a load event you inflicted on someone by trying to be resilient.