Every other policy in this domain computes its delay from the attempt number, so a client’s whole schedule is fixed the moment it starts failing. This one is a random walk: each delay is drawn from a range reaching up to three times the previous delay, and the sequence wanders upward rather than climbing a fixed ladder.
delay = min(cap, base + rng.nextInt(prev * 3 - base + 1))
prev = delay
On the canonical workload it records the lowest peak simultaneous retries of any policy here: 1.1%, against full jitter’s 2.6% and plain exponential’s 15.1%. Because whole schedules diverge rather than individual attempts, two clients that started together are at very different points a few attempts in.
When to use it
- When dispersing a large fleet matters most. This is the strongest decorrelation on offer, and the metric says so.
- Behind a dependency that many clients share. The longer the outage runs, the further apart the walks drift, so the pressure keeps spreading rather than settling into a periodic pattern.
- When you can tolerate an unpredictable schedule. A client might reach the cap in six attempts or eighty-seven. If nothing downstream depends on the timing, that spread is pure benefit.
- When per-client state is acceptable. One integer, but it must live as long as the retry sequence.
When not to use it
- When the delay sequence must be predictable. This is the only policy here whose schedule you cannot state in advance, even in distribution over a fixed attempt count. Debugging a slow retry is correspondingly harder.
- When a policy object cannot be kept alive across attempts. The walk lives
in
prev. A policy reconstructed per attempt restarts atbaseevery time and degenerates into a fixed-range draw. If your retry loop builds the policy inside it, use full jitter. - When you want the shortest time to success. It has the longest mean time to success of the jittered policies on the benchmark, because it climbs slowly on average. See below.
- As a default. full jitter is simpler, stateless, and close enough on the herd metric for most fleets.
How it works
One thing here is easy to get wrong, so it comes first.
It climbs more slowly than doubling, despite reaching for three times the last
delay. The range looks aggressive, and the natural reading is that this
policy backs off faster than exponential. It does not. The draw is uniform over
[base, 3 x prev], so the expected step is (base + 3 x prev) / 2: about
1.5x, measured at 1.53 over a long run, against exponential’s exact 2x.
The consequence is measurable: reaching a 10-second cap from a 100 ms base takes a median of 14 attempts, where exponential reaches it at attempt 8 every time. This page originally said the opposite, and a test caught it. The test now records the real figures.
The variance is the point, not the speed. Those 14 attempts range from 6 to 87 across clients. Exponential’s 8 is 8 for everyone.
prev starts at base, which is where the walk begins, and the span
3 x prev - base is therefore always at least 2 x base: positive for any
valid configuration, so the draw never has an empty range.
The walk advances to the delay actually used, cap included. Advancing to the uncapped draw instead would leave a client at the ceiling drawing from an ever-growing range it can never reach: the delay would be pinned but the distribution would keep climbing, which is meaningless state. A vector pins the capped behaviour.
The attempt number is ignored except for the give-up check. Asking twice with the same attempt number gives two different answers that both advance the walk. A vector pins that too, and it is what distinguishes this policy from every other one here.
Parameters
Complexity
O(1) time: no loop at all, unlike the policies that recompute an exponential ceiling. O(1) space: a single integer of state.
Source
Marc Brooker, Exponential Backoff And Jitter, AWS Architecture Blog, 2015,
where it appears as sleep = min(cap, random_between(base, sleep * 3)). That
write-up found it competitive with full jitter on completion time while doing
less total work.
Its neighbours are full jitter, which spreads each attempt independently from a fixed ceiling, and equal jitter, which spreads half of one. This spreads the whole schedule, which is why it wins the herd metric and loses the latency one.
Notes
No patents known.
The Rng is supplied at construction rather than passed to nextDelay (see the
domain interface for why). For this policy that matters more than for the
others: the object holds the walk, so its lifetime is the retry sequence.