Exponential backoff with jitter: most teams add too little

Automation Engineering · By Caleb Sakala · June 19, 2026

A chaotic pile-up of identical cartoon wind-up robots all jamming a single subway turnstile at once

Open almost any retry helper and you will find the same line: wait 2 ** attempt seconds, then add a few hundred milliseconds of random noise. That noise is jitter, and the instinct behind it is right. What teams get wrong is how much. Exponential backoff with jitter is the standard cure for retry storms, yet a 200-millisecond random window does almost nothing when a thousand clients all failed in the same second. The randomness has to be wide enough to pull the herd apart, and most code keeps it narrow out of a vague worry that big random delays will hurt latency. The measured results point the other way.

Backoff alone just moves the pileup

Marc Brooker laid this out in 2015 on the AWS Architecture Blog, and the argument has aged well enough that AWS still links to it from the Builders' Library. He simulated optimistic concurrency control: a batch of clients all trying to update the same database row, over a network with mean delay of 10ms and variance of 4ms. With no backoff at all, one client wins each round, so finishing every update takes N rounds and the total work scales with N squared.

Capped exponential backoff is the textbook response. Multiply the wait by a constant after each failure, then stop climbing at some ceiling. Run the simulation with it and the result disappoints: client work drops only a little. Plot the moments when retries actually land and the reason jumps out. The calls still arrive in tight clusters. Backoff pushed the clusters further apart in time, but every client inside a cluster still woke at the same moment, so they still collided. The pileup moved. It did not shrink.

How much exponential backoff with jitter buys you

Jitter is the fix, and it costs one line. Instead of sleeping for the full backoff interval, sleep for a random amount between zero and that interval. Brooker calls this Full Jitter. With 100 contending clients, it cut the call count by more than half against un-jittered backoff, and it finished sooner on top of that. The no-jitter version ran so much slower he dropped it from the comparison chart entirely so the other lines stayed readable.

Here is the part that gets lost. The win comes from the width of the random range, not from the shape of the backoff curve. Full Jitter draws across the entire interval, zero up to the cap. That width is what flattens a spike of a thousand simultaneous retries down to a near-constant trickle.

Full, equal, and decorrelated jitter, side by side

Brooker tested three variants. Here they are as plain pseudocode, with base the starting delay and cap the ceiling:

# Full jitter: random across the whole interval
sleep = random_between(0, min(cap, base * 2 ** attempt))

# Equal jitter: keep half the backoff, randomize the other half
temp  = min(cap, base * 2 ** attempt)
sleep = temp / 2 + random_between(0, temp / 2)

# Decorrelated jitter: grow the random ceiling from the last sleep
sleep = min(cap, random_between(base, sleep * 3))

Two of the three cases have a clean verdict. No-jitter backoff loses on both work and time. Equal Jitter, the cautious-looking compromise that always keeps half the delay, comes last among the jittered options: it does slightly more work than Full Jitter and takes noticeably longer. Between Full and Decorrelated the call is genuinely close. Full Jitter does the least total work; Decorrelated finishes faster because its delays can grow past a single capped interval. Protecting a fragile downstream service? Optimize for work and pick Full. Care most about p99 completion under heavy contention? Decorrelated has the edge. Reaching for Equal because it feels safer is the one choice the data argues against.

The timid-jitter trap

Here is the version that ships in real codebases: base * 2 ** attempt plus random(0, 200ms). That fixed 200ms band breaks down under load. When the backoff interval has grown to eight seconds, a 200ms wobble smears a thousand clients across 200ms, which is still a wall of traffic hitting at once. Full Jitter would have spread those same clients across the full eight seconds. Jitter has to scale with the interval, not perch as a constant on top of it. A random range that stays tiny while the backoff climbs is just decoration.

Retries are not the only thing that clusters

Jitter earns its keep well outside retry loops. Periodic jobs are the quiet version of the same trap. A fleet of servers all running a task once a minute will fire in the first second of the minute, because their clocks agree with each other. Brooker's teams on EBS and Lambda found that spreading those start times let them do the same work with less server capacity.

A Chase Agents automation on a five-minute cron trigger shows the pattern cleanly. Across a workspace where several scheduled runs poll the same vendor API, every run lands at :00 and :05 and buries that endpoint in synchronized requests. Offset each run by a random slice of its interval and one spike turns into steady flow, and the vendor stops handing you 429s.

Layering multiplies the damage further. Brooker's Builders' Library piece walks through a five-deep call stack with three retries at each layer: when the database at the bottom starts failing, the retries compound, three then nine then twenty-seven, until the load arrives at 243 times normal. Defend against that by retrying at one layer only. A procurement workflow that calls a flaky pricing service makes the fix concrete. If the research step, the approval step, and the orchestrator each retry on their own, one slow vendor balloons into hundreds of calls. Route every retry through a single orchestration step instead, and the amplification stops at one layer. That single-owner discipline is how Chase Agents structures tool access through action-type routing, where only the orchestrator holds the tool that fires the external call, so a retry can't fan out across every step in the workflow.

Go read the jitter line in your own retry helper. If the random term is a fixed handful of milliseconds sitting next to an exponential term that climbs into the seconds, it is not doing the job you think it is. Change it to draw from zero up to the full interval, point it at a hundred simulated clients, and watch the call count drop. Brooker's simulator is open source, so you can confirm the numbers before you trust them.