Compute the exponential backoff ceiling as usual, min(cap, base * 2^(n-1)),
and then wait a uniform random amount between zero and that, rather than the
ceiling itself.
This is the domain’s recommended default. It keeps everything exponential backoff gets right, and fixes the thing it gets wrong: clients no longer retry in lockstep, because each picks its own delay.
On the canonical workload it cuts the peak simultaneous retries from about 15% of all retries to about 2.6%, roughly a sixfold reduction in the spike a recovering service feels.
When to use it
- By default, in anything with more than one client. If you are choosing a backoff policy and have no specific reason to choose otherwise, this is it.
- Wherever many clients can fail at the same moment. A dependency that goes down takes every caller with it, and they all start their backoff clocks together. Un-jittered policies keep them together.
- In front of anything with a warm-up cost: a connection pool refilling, a cache repopulating, a JIT re-optimising. These recover far better under a smooth trickle than under a periodic wall of requests.
- Whenever you would otherwise write exponential backoff. The change is one line and it has no downside except the one below.
When not to use it
- When you need a reproducible delay sequence without a random source. Some embedded and audit contexts genuinely require the next delay to be derivable from the attempt number alone, and exponential is that policy.
- When halving the expected delay is too aggressive. A uniform draw from
[0, c]averagesc/2, so this waits about half as long as plain exponential. See below. If your service needs the full backoff duration, equal-jitter keeps half the delay fixed and jitters the rest. - With a single client. Jitter disperses a fleet. One caller retrying alone gains nothing from it and loses a little patience.
- When the server told you when to come back. A
Retry-Afterheader is the server’s own estimate, and guessing over it is worse than reading it: retry-after-aware does.
How it works
if not error.retryable: return null
if attempt >= maxAttempts: return null
ceiling = min(capMs, baseMs * 2^(attempt - 1))
return rng.nextInt(ceiling + 1) # uniform over [0, ceiling]
The bound is ceiling + 1, because nextInt(n) returns 0 .. n-1. The
ceiling itself has to remain reachable, and a test checks that it occurs.
Zero is reachable, and that is deliberate. A client retrying immediately is part of what makes the arrival pattern smooth rather than merely delayed. A policy that never returned zero would be shifting the herd, not dispersing it.
Giving up consumes no randomness. The two refusal checks come before the draw, so a policy that declines a call leaves its stream exactly where it was. A port that ordered this differently would diverge silently from the first give-up onward, so a test pins it.
The ceiling is shared with exponential rather than
restated, so the two policies can never drift apart in what they compute before
the draw. It doubles by repeated multiplication and stops at the cap, which
means the arithmetic cannot overflow however large the attempt number grows:
base << (attempt - 1) would be nonsense at attempt 40.
Parameters
Complexity
O(attempt) time: a bounded loop that stops at the cap, so in practice a handful of multiplications. O(1) space. The policy holds nothing between calls except its parameters and its random stream.
Source
Marc Brooker, Exponential Backoff And Jitter, AWS Architecture Blog, 2015, which is where the name comes from and which measured the variants against each other on a contended workload. Full jitter both completed sooner and did less total work than the un-jittered version, a rare case where the more random option wins on every axis at once.
The uncomfortable half of the result, which this registry reports rather than hides: on our benchmark full jitter succeeds less often than plain exponential (about 0.20 against 0.40) because its expected delay is half, so it exhausts the same attempt budget in half the elapsed time. It is still the right default, because the axis it wins on, not synchronising a fleet, is the one that turns an outage into an outage that does not end. But a reader who discovered that number independently would rightly distrust a page that had not mentioned it, and a test asserts the ordering so it cannot quietly change.
Its neighbours are exponential, which is this policy without the draw, and equal-jitter, which draws over only half the range.
Notes
No patents known.
The Rng is supplied at construction rather than passed to nextDelay, which
departs from Every other domain in the registry supplies
randomness at construction, as the C vtable’s create(params, allocator, rng)
already does, and making retry the exception would put an pb_rng * on the C
hot path for no benefit. The property that matters, that a policy never reaches
for a global source, is unchanged.