Skip to main content

Exponential Backoff with Jitter

Exponential Backoff with Jitter randomizes retry delays so concurrent clients do not retry in lockstep, preventing thundering-herd traffic against a recovering service. Full jitter and decorrelated jitter are the recommended defaults for high-concurrency systems.

Type
Resilience
When to Use
High Concurrency Retries, Thundering Herd Risk, Shared Dependency, Cloud APIS

When a popular service recovers from a brief outage, every client that was failing tends to retry at the same moment. Plain exponential backoff helps spread retries over time, but it does not break the synchronization: if all clients failed at the same instant and double their delay identically, their retries still align. The result is a periodic wave of traffic — a thundering herd — that can knock the service down again. Exponential Backoff with Jitter adds randomness to each delay so retries scatter across the time window.

How It Works

Start from an exponential schedule: base * 2^attempt, capped at a maximum. Then perturb the value with a random component. Common strategies, popularized by an AWS Architecture Blog analysis, are:

  • Full jitter: sleep = random(0, min(cap, base * 2^attempt)). Each delay is uniformly random between zero and the exponential ceiling. This spreads retries most aggressively and is usually the best default.
  • Equal jitter: temp = min(cap, base * 2^attempt); sleep = temp/2 + random(0, temp/2). Guarantees a minimum wait while still randomizing.
  • Decorrelated jitter: sleep = min(cap, random(base, prev_sleep * 3)). Each delay derives from the previous one, producing a smooth, well-distributed sequence.

The AWS analysis showed full jitter and decorrelated jitter dramatically reduce contention and total work compared with no jitter, while keeping completion times competitive.

When to Use It

Use jitter whenever many independent clients retry a shared dependency: cloud APIs, databases, lock acquisition, leader election, and message brokers. It is the recommended default for any retry policy in a system with meaningful concurrency. For a single client calling a private service, plain backoff may suffice, but jitter rarely hurts.

Trade-offs

Jitter makes individual retry timing nondeterministic, which can complicate testing and reasoning about worst-case latency. Full jitter can occasionally retry very quickly, so combine it with an attempt cap. The benefit — avoiding self-inflicted retry storms — almost always outweighs the loss of determinism in distributed settings.

Related Patterns

This is a refinement of retry-with-backoff. It works alongside circuit-breaker to stop retrying a dead dependency, rate-limiter to bound outbound request rate, and load-shedding on the server side to protect against herds that jitter alone cannot fully tame.

Example

Decorrelated jitter in pseudocode:

base = 100ms; cap = 20s; sleep = base
for attempt in 1..max:
  try: return call()
  catch Retryable:
    sleep = min(cap, random(base, sleep * 3))
    wait(sleep)
throw Failed

Resilience4j, Polly, and the AWS/GCP SDKs all support jittered backoff; enabling it is often a single configuration flag.