Overview
Retry mechanisms re-attempt failed operations to recover from transient errors such as timeouts, dropped connections, throttling, or brief outages. Done naively, retries make things worse: immediate retries from thousands of clients at once (a retry storm) can overwhelm a struggling service and prevent it from recovering.
Good retries use exponential backoff (increasing delays), jitter (randomization to spread clients out), a maximum number of attempts, retry budgets, and only retry idempotent operations or errors known to be transient. In messaging systems, retries are implemented with redelivery, delay queues, and retry topics, ending in a dead-letter queue.
If a line is busy, you do not redial every second; you wait a minute, then five, then fifteen. If everyone waited the exact same time, they would all call back at once, so each person adds a random delay.
When to use it
- Network calls between services.
- Message consumers handling transient failures.
- Cloud APIs returning throttling errors (429, 503).
- Database connection hiccups and failovers.
Where it shows up in interviews
Recognize it when: downstream sometimes times out.
- Design a payment gateway integration
- Design a microservice client library
Recognize it when: failed messages should be retried later.
- Design webhook delivery
- Design a notification system
Where it is used in real software
Retry with exponential backoff and jitter by default; AWS published analysis showing 'full jitter' performs best.
Retries failed webhook deliveries with exponential backoff for up to three days.
Configure retries with per-try timeouts and retry budgets in the service mesh.
Key terms
- Exponential backoff
- Delay doubles each attempt: 100 ms, 200 ms, 400 ms...
- Jitter
- Random variation in delays to avoid synchronized retries.
- Retry budget
- Limit retries to a percentage of total requests.
- Retry storm
- Retries amplifying load on a failing service.
- Retryable error
- Transient failure likely to succeed later.
How it works, step by step
- 1Classify the error
Retry timeouts, 429, 503; do not retry 400 or 401.
- 2Check idempotency
Only retry operations that are safe to repeat, or use idempotency keys.
- 3Wait with backoff and jitter
delay = random(0, min(cap, base x 2^attempt)).
- 4Respect limits
Max attempts, overall deadline, retry budget, Retry-After headers.
- 5Give up gracefully
Return an error, fall back, or send to a DLQ.
Backoff schedules
Base 100 ms, cap 5 s
| Attempt | No backoff | Exponential | Exponential + full jitter |
|---|---|---|---|
| 1 | 0 ms | 100 ms | random 0-100 ms |
| 2 | 0 ms | 200 ms | random 0-200 ms |
| 3 | 0 ms | 400 ms | random 0-400 ms |
| 4 | 0 ms | 800 ms | random 0-800 ms |
| 5 | 0 ms | 1,600 ms | random 0-1,600 ms |
NOWAttempt: 1 | No backoff: 0 ms | Exponential: 100 ms | Exponential + full jitter: random 0-100 ms
Jitter spreads thousands of clients over time, turning a synchronized spike into smooth load that the service can recover from.
Implementation
const RETRYABLE = new Set([408, 429, 500, 502, 503, 504]); export async function retry<T>(fn: () => Promise<T>, { attempts = 5, baseMs = 100, capMs = 5000, deadlineMs = 10_000 } = {}) { const start = Date.now(); for (let attempt = 0; ; attempt++) { try { return await fn(); } catch (err) { const status = (err as { status?: number }).status; const retryable = status === undefined || RETRYABLE.has(status); // network errors have no status if (!retryable || attempt + 1 >= attempts) throw err; const retryAfter = Number((err as { retryAfterMs?: number }).retryAfterMs ?? 0); const backoff = Math.random() * Math.min(capMs, baseMs * 2 ** attempt); // full jitter const delay = Math.max(retryAfter, backoff); if (Date.now() - start + delay > deadlineMs) throw err; await new Promise((r) => setTimeout(r, delay)); } }} // Usage: retry(() => paymentsApi.charge(req, { idempotencyKey }))Complexity and performance
Retries at every layer multiply.
Bound with a deadline.
Trade-offs
Retries recover from blips but multiply load during outages; combine with circuit breakers and budgets.
Retries add latency; set per-try timeouts and an overall deadline.
Variants and related techniques
Send a second request after a delay and use the first response.
Asynchronous retries with delay queues or scheduled topics.
Common mistakes
- Retrying at every layer.
Fix: 3 layers x 3 retries = 27x load; retry at one layer (usually the edge or client).
- Retrying non-idempotent operations.
Fix: Use idempotency keys first.
- No jitter.
Fix: Synchronized retries hit the service in waves.
Interview questions
Why add jitter to exponential backoff?
Without jitter, clients that failed at the same moment retry at the same moments, creating repeated load spikes. Random delays spread retries out so the service can recover.
How do retries cause cascading failures?
When a service slows, callers retry, increasing its load further; if every layer retries, load multiplies. Use backoff, jitter, retry budgets, circuit breakers, and retry at only one layer.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Implement retry with full jitter | Easy | Backoff math. |
| Design webhook delivery with retries for 3 days | Medium | Schedules and DLQs. |