MESSAGING & EVENT-DRIVEN ARCHITECTURE / SYSTEM CONCEPT BRIEF

Retry mechanisms

Retry mechanisms re-attempt failed operations to recover from transient errors such as timeouts, dropped connections, throttling, or brief outages.

IntermediatePhase 06 / Topic 15 of 18RequirementsTrade-offsFailure modes
01

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.

Calling a busy phone line

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.

02

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.
03

Where it shows up in interviews

Resilient service calls

Recognize it when: downstream sometimes times out.

  • Design a payment gateway integration
  • Design a microservice client library
Async retries

Recognize it when: failed messages should be retried later.

  • Design webhook delivery
  • Design a notification system
04

Where it is used in real software

AWS SDKs

Retry with exponential backoff and jitter by default; AWS published analysis showing 'full jitter' performs best.

Stripe webhooks

Retries failed webhook deliveries with exponential backoff for up to three days.

Envoy and Istio

Configure retries with per-try timeouts and retry budgets in the service mesh.

05

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.
06

How it works, step by step

  1. 1
    Classify the error

    Retry timeouts, 429, 503; do not retry 400 or 401.

  2. 2
    Check idempotency

    Only retry operations that are safe to repeat, or use idempotency keys.

  3. 3
    Wait with backoff and jitter

    delay = random(0, min(cap, base x 2^attempt)).

  4. 4
    Respect limits

    Max attempts, overall deadline, retry budget, Retry-After headers.

  5. 5
    Give up gracefully

    Return an error, fall back, or send to a DLQ.

07

Backoff schedules

Base 100 ms, cap 5 s

Step 1 / 5
AttemptNo backoffExponentialExponential + full jitter
10 ms100 msrandom 0-100 ms
20 ms200 msrandom 0-200 ms
30 ms400 msrandom 0-400 ms
40 ms800 msrandom 0-800 ms
50 ms1,600 msrandom 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.

08

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 }))
09

Complexity and performance

Load amplificationUp to attempts^depth

Retries at every layer multiply.

Worst-case latencySum of delays + attempts x timeout

Bound with a deadline.

10

Trade-offs

Recovery vs amplification

Retries recover from blips but multiply load during outages; combine with circuit breakers and budgets.

Latency

Retries add latency; set per-try timeouts and an overall deadline.

11

Variants and related techniques

Hedged requests

Send a second request after a delay and use the first response.

Retry queues

Asynchronous retries with delay queues or scheduled topics.

12

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.

13

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.

14

Practice problems

ProblemDifficultyWhat it trains
Implement retry with full jitterEasyBackoff math.
Design webhook delivery with retries for 3 daysMediumSchedules and DLQs.