Retries and Backoff: Repeating Work Without Making Failure Worse
📑 On this page
- A concrete example: an unavailable worker API
- Transient and permanent failures
- Retry classification
- Retry amplification
- Immediate retries
- Fixed backoff
- Exponential backoff
- Jitter
- Retry limits
- Retry budgets
- Idempotency
- Read operations
- Writes and side effects
- Respecting server guidance
- Retrying another endpoint
- Hedged requests
- Queue retries
- User-initiated retry
- Observability
- Testing retries
- Knowledge check
- The one idea to remember
Many network failures are temporary.
A packet is lost, a server restarts, a connection pool briefly fills, or a rate limiter asks the caller to slow down. Trying again can turn a momentary problem into success.
A retry is useful only when another attempt may succeed and repeating the operation will not create unacceptable consequences.
Uncontrolled retries multiply traffic precisely when a dependency is least able to handle it.
A concrete example: an unavailable worker API
A background worker calls an image-processing service and receives a temporary unavailable response.
Instead of sending hundreds of attempts immediately, it:
- records the failure,
- waits one second,
- retries,
- waits about two seconds after another failure,
- adds random variation,
- stops after a fixed attempt or time budget,
- and sends the job to a failure queue if recovery never occurs.
The delay gives the dependency room to recover.
Transient and permanent failures
A transient failure may disappear:
- connection reset,
- temporary overload,
- service unavailable,
- leader election,
- or rate limiting.
A permanent failure needs a change:
- invalid credentials,
- malformed input,
- missing permission,
- unsupported operation,
- or a deleted resource.
Repeating permanent failures wastes capacity and delays useful error handling.
Retry classification
Do not classify only by broad status code.
For example, one conflict might be retriable after refreshing state, while another represents a permanent business rejection. A timeout may be retriable only when the operation is idempotent.
The API contract should state which failures callers may retry and under what conditions.
Retry amplification
Suppose a user request crosses three services, and each layer retries three times.
One original request can produce as many as:
3 × 3 × 3 = 27downstream attempts. During an outage, this amplification consumes the remaining capacity and slows recovery.
Prefer one well-informed retry layer rather than independent repetition everywhere.
Immediate retries
An immediate retry can help if the first failure came from a one-time connection issue and another healthy endpoint is available.
Repeated immediate retries against an overloaded service are harmful because conditions have not changed. They create a tight failure loop.
Most repeated attempts should include delay.
Fixed backoff
Fixed backoff waits the same interval between attempts:
1s, 1s, 1s, 1sIt is simple and may suit low-volume polling. At scale, many clients that fail together can remain synchronized and retry together every second.
That synchronization creates repeated traffic spikes.
Exponential backoff
Exponential backoff increases delay after each failure:
1s, 2s, 4s, 8s, 16sThis quickly reduces pressure on a struggling dependency. A maximum delay prevents intervals from becoming impractically large.
The sequence should also respect the operation's total deadline.
Jitter
Jitter adds randomness to retry timing.
Instead of every client retrying at exactly four seconds, each selects a value from a defined range. Traffic spreads over time, reducing synchronized bursts known as a thundering herd.
Common strategies include full jitter, equal jitter, and decorrelated jitter. The exact formula matters less than preventing lockstep behavior and testing expected bounds.
Retry limits
Every retry policy needs a stopping condition:
- maximum attempts,
- maximum elapsed time,
- operation deadline,
- or explicit cancellation.
Infinite retry can keep obsolete work alive, grow queues, and hide permanent failure.
Stopping should lead to a visible state: failure queue, user message, alert, or manual recovery.
Retry budgets
A retry budget limits the proportion of extra traffic retries may create.
For example, a service might permit retry traffic up to ten percent of normal requests. When broad failure occurs, the budget prevents all clients from multiplying load.
Budgets protect system capacity better than per-request limits alone.
Idempotency
Retries are safer when repeating a request does not repeat its effect.
For a payment, the client reuses the same idempotency key. The server recognizes the logical operation and returns its original result.
Without idempotency, a lost success response can turn a reasonable retry into a duplicate charge.
Read operations
Reads are often safer to retry because they do not intentionally change state.
But not every request labeled GET is harmless, and reads can still be expensive. A retry storm against a large report query can overload the database.
Protocol method alone does not replace knowledge of cost and side effects.
Writes and side effects
Writes need explicit duplicate handling.
Possible approaches include:
- idempotency keys,
- natural unique constraints,
- compare-and-set versions,
- deduplication records,
- or operation identifiers stored with the result.
If repetition cannot be made safe, uncertain outcomes may require status lookup or human reconciliation instead of automatic retry.
Respecting server guidance
Rate-limited or overloaded services may return a Retry-After value or equivalent guidance.
Clients should respect it within their own deadline and safety policy. Ignoring server advice defeats coordinated recovery.
Guidance still needs jitter when many clients receive the same timestamp.
Retrying another endpoint
A load-balanced client may retry against another healthy instance.
This helps when one instance is broken but can spread overload when the problem is shared, such as a failed database. Endpoint switching is not a substitute for backoff and system-level health information.
Each extra attempt consumes latency budget.
Hedged requests
A hedged request sends a second copy after the first exceeds a latency threshold, then uses the earliest result.
This can reduce tail latency for safe reads, but it deliberately increases traffic. Use it only with capacity controls, idempotent operations, and cancellation of unnecessary copies.
Hedging is not a general-purpose fix for slow systems.
Queue retries
Message consumers often retry failed jobs.
A robust design includes:
- attempt count,
- increasing delay,
- visibility timeout or acknowledgment rules,
- dead-letter destination,
- idempotent processing,
- and a way to inspect and replay safely.
A poison message that always fails should not block healthy work forever.
User-initiated retry
A "Try again" button is still a retry policy.
The interface should:
- preserve entered data,
- prevent rapid duplicate submissions,
- clarify uncertain status,
- and reuse an idempotency key when necessary.
Telling a user to retry without protecting side effects transfers system complexity to them.
Observability
Measure:
- original attempts,
- retry attempts,
- success after retry,
- final failure,
- delay distribution,
- reason classification,
- and amplification by dependency.
A high retry-success rate can still indicate an underlying reliability problem that deserves repair.
Testing retries
Test:
- success on a later attempt,
- permanent failure with no retry,
- exhaustion,
- deadline expiration,
- jitter bounds,
- duplicate success responses,
- cancellation,
- and server-provided retry delay.
Use a controllable clock so tests do not wait real minutes.
Knowledge check
- What distinguishes a transient failure from a permanent one?
- How can retries amplify through a service chain?
- Why does jitter matter even with exponential backoff?
- What is a retry budget?
- Why can a user-facing Try Again button create duplicate side effects?
The one idea to remember
Retry only failures that may improve, and stop within a clear budget. Use backoff and jitter to avoid synchronized overload, respect server guidance, and make repeated side effects idempotent or explicitly reconcilable.