Retry, timeout and backoff: failing well when the network does not
Retry only safe operations, wait longer each time with exponential backoff and jitter, respect Retry-After, and use timeouts that really stop the work.
Comes up in: Coding, Backend design
Learn#
Leela’s payment page retried a failed request three times, at once. The server had been slow, not down, so the customer was charged twice and the server got even busier.
A retry helps only with temporary failures, such as a timeout, a 503 or a dropped connection. Retry only idempotent operations: running them twice has the same effect as once. A GET is; a payment is not, unless it carries an idempotency key.
Exponential backoff doubles the wait each time: 500 ms, 1 s, 2 s, 4 s. Jitter picks a random time below that cap, so thousands of clients do not retry at the same moment.
Network
- GET /orders → 503
State
- attempt = 1
- wait = random(0–500) = 310 ms
Attempt 1 fails with 503. The cap for the first wait is 500 ms; jitter picks a random time below it, here 310 ms.
Read the steps as text
- Attempt 1 fails with 503. The cap for the first wait is 500 ms; jitter picks a random time below it, here 310 ms.
- Attempt 2 fails. The cap doubles to 1,000 ms; jitter picks 720 ms.
- Attempt 3 gets 429 with Retry-After: 2. The server asked for 2 seconds, so we wait 2,000 ms.
- Attempt 4 succeeds. If it had failed again at the attempt limit, we would stop and show the error.
A timeout built with Promise.race gives up waiting, but the slow work keeps running:
let finished = false;
const slowSave = new Promise((r) => setTimeout(() => { finished = true; r("saved"); }, 100));
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("timed out")), 50));
try { await Promise.race([slowSave, timeout]); }
catch (e) { console.log(e.message, "| finished:", finished); }
await new Promise((r) => setTimeout(r, 100));
console.log("later, finished:", finished);Output (verified)
timed out | finished: false later, finished: true
Predict#
Predict first
All 10,000 clients get a 503 at the same second and retry with backoff but no jitter. What happens?
Without jitter, every client computes the same delay, so retries arrive in synchronized waves. This is the “thundering herd”. Growing waits are tempting to trust, but only randomness spreads the load.
Interview angle#
Say it like this — about 30 seconds
“I retry only idempotent requests, or ones with an idempotency key, and only on temporary errors such as timeouts, 429 and 5xx, never on a 400. Each wait is a random time up to base times two to the attempt, capped, so clients do not retry together. I stop after a few attempts and respect Retry-After. Timeouts use an abort signal, because Promise.race leaves the slow request running. Under a long outage, a circuit breaker stops calls for a while.”
Follow-up If Promise.race is used for a timeout, what happens to the slower promise?
Follow-up Which errors should you not retry?
Follow-up What is a circuit breaker?
Practice#
Timed, like the real round
Implement retryWithBackoff with full jitter, an attempt limit and Retry-After support.
Done when
- It stops after
retries + 1calls and rejects with the last error. - Waits follow
random() * min(max, base * 2 ** (n - 1)). error.retryAfterMsoverrides the computed wait.shouldRetry(error) === falsestops at once.
Remember#
Try to answer each card before you open it. Then add them to your reviews.
What does idempotent mean?
Doing it twice has the same effect as doing it once.
Why add jitter to backoff?
So many clients do not retry at the same moment.
What happens to the slow promise when Promise.race times out?
It keeps running; only its result is ignored.
Which status codes are usually worth retrying?
429, 503 and other temporary 5xx errors, and timeouts.
Go deeper
Four layers. Stop at the one your interview needs; each ends with a quick check.
The core ideaEvery round
base * 2 ** (n - 1), limited by a maximum.Check yourself: What is the wait cap for retry 4 with base 500 ms and no max?
500 × 2³ = 4,000 ms.
How it works underneathSenior rounds
“Full jitter” waits a random time between zero and the cap. AWS found it spreads load well while keeping total work low Source: AWS Architecture Blog: Exponential backoff and jitter.
Check yourself: In full jitter, what is the wait for a cap of 1,000 ms?
A random value between 0 and 1,000 ms.
Edge cases and trapsWhere people slip
Retry-After: seconds, or an HTTP date. Use it instead of your own delay Source: RFC 9110: Retry-After.Check yourself: Retry-After can hold two kinds of value. Which?
A number of seconds, or an HTTP date.
In productionStaff-level answers
Check yourself: What does a circuit breaker do in the open state?
Fails calls at once without calling the service, until a cool-down ends.
Finished the five steps? Mark it, and its cards join your reviews.
Last checked 2026-10-31. Spot a mistake? Use “Report an error” below — fixes are listed on the corrections page.
Interview questions this answers
- Implement retry with exponential backoff.
- If
Promise.raceis used for a timeout, what happens to the slower promise?