Retry with Backoff
Retry with Backoff re-attempts failed operations after increasing delays, recovering from transient faults while avoiding the load amplification of fixed-interval retries. It is most effective on idempotent operations and combined with timeouts, jitter, and circuit breakers.
Distributed systems fail in transient ways. A network packet is dropped, a database briefly hits a connection-pool limit, or a service is mid-deploy. Many of these faults clear on their own within milliseconds to seconds. The Retry with Backoff pattern automates recovery: when an operation fails with a retryable error, the caller waits and tries again, increasing the wait between attempts.
The core idea is that the delay grows with each attempt rather than staying fixed. Fixed-interval retries from many clients can synchronize into a thundering herd that keeps a recovering service down. Backoff spreads load over time and gives the dependency room to recover.
How It Works
On failure, the caller checks whether the error is retryable. Network timeouts, connection resets, HTTP 429, and HTTP 503 typically are; HTTP 400 or 401 are not. If retryable and the attempt budget is not exhausted, the caller sleeps for a delay and tries again.
The delay schedule defines the strategy:
- Constant: fixed delay (e.g. 200 ms) between attempts.
- Linear: delay grows by a fixed step (200 ms, 400 ms, 600 ms).
- Exponential: delay doubles each attempt (200 ms, 400 ms, 800 ms), usually capped at a maximum.
A retry policy also bounds the work: a maximum attempt count, a total time budget, or both. Without bounds, retries amplify load and can turn a brief blip into a sustained outage.
When to Use It
Retry transient, recoverable faults on operations that are safe to repeat. It fits remote calls, cloud storage requests, message publishing, and database queries. Pair it with idempotency so that a retried write does not duplicate side effects. Do not retry deterministic failures such as validation errors, authorization denials, or malformed requests — retrying only wastes resources.
Trade-offs
Retries trade latency for success rate. A caller that retries three times can take far longer to return an error than one that fails immediately. Aggressive retries also amplify load: each client retry multiplies traffic to an already-stressed dependency, a failure mode known as retry storms. Combining retries with a circuit breaker and jitter limits this risk. Retries on non-idempotent operations can cause duplicate charges, double-sent emails, or corrupted state, so idempotency keys are often a prerequisite.
Related Patterns
Retry pairs naturally with circuit-breaker (stop retrying when a dependency is clearly down), timeout-pattern (bound each attempt), and exponential-backoff-jitter (randomize delays to avoid synchronization). idempotency-key makes retried writes safe.
Example
A pseudocode retry loop:
attempt = 0
delay = 200ms
while attempt < 5:
try:
return call_service()
catch RetryableError:
sleep(delay)
delay = min(delay * 2, 5s)
attempt += 1
throw MaxRetriesExceeded
Most cloud SDKs (AWS SDK, Google Cloud client libraries, gRPC) ship retry-with-backoff by default. Libraries such as Polly (.NET), Resilience4j (Java), and Tenacity (Python) make the policy explicit and configurable.