Design Patterns

Retry Pattern

Understanding the retry pattern for handling transient failures.

3 min readConcepts · System Design

What is the Retry Pattern?

The Retry Pattern automatically re-attempts a failed operation, assuming the failure is transient (temporary). Network blips, timeouts, and brief service outages often resolve themselves within seconds.


When to Retry

RetryDon't Retry
Network timeout400 Bad Request
503 Service Unavailable401 Unauthorized
429 Too Many Requests404 Not Found
Connection resetBusiness logic error
DNS resolution failureData validation error

Retry Strategies

Immediate Retry

Immediate RetryFlowchart

Simple but can overwhelm an already struggling service.

Fixed Delay

Attempt 1 → fail → wait 2s
Attempt 2 → fail → wait 2s
Attempt 3 → fail → wait 2s
Attempt 4 → fail → give up

Exponential Backoff

Attempt 1 → fail → wait 1s
Attempt 2 → fail → wait 2s
Attempt 3 → fail → wait 4s
Attempt 4 → fail → wait 8s
Attempt 5 → fail → give up

Exponential Backoff with Jitter

Attempt 1 → fail → wait 1s + random(0-500ms)
Attempt 2 → fail → wait 2s + random(0-500ms)
Attempt 3 → fail → wait 4s + random(0-500ms)

Jitter prevents the thundering herd problem where all clients retry simultaneously.


Comparison

StrategyDelayBest For
ImmediateNoneVery transient failures
FixedConstantKnown recovery times
ExponentialIncreasingUnknown recovery times
Exponential + JitterIncreasing + randomHigh-concurrency systems

The Thundering Herd Problem

The Thundering Herd ProblemSequence diagram

Code Example

async function retryWithBackoff(fn, options = {}) {
  const {
    maxRetries = 3,
    baseDelay = 1000,
    maxDelay = 30000,
    jitter = true
  } = options;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries || !isRetryable(error)) {
        throw error;
      }

      let delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
      if (jitter) {
        delay += Math.random() * delay * 0.5;
      }

      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

function isRetryable(error) {
  const retryableCodes = [408, 429, 500, 502, 503, 504];
  return retryableCodes.includes(error.statusCode);
}

Retry + Circuit Breaker

These patterns complement each other:

Retry + Circuit BreakerFlowchart

Retry Budgets

Limit total retries across all clients:

ApproachDescription
Per-request limitMax 3 retries per request
Per-client limitMax 10 retries per second
System-wide budgetTotal retries < 10% of requests

Best Practices

  1. Only retry idempotent operations (or use idempotency keys)
  2. Set a maximum retry count to avoid infinite loops
  3. Use exponential backoff with jitter for distributed systems
  4. Log retry attempts for debugging
  5. Combine with circuit breaker to fail fast when service is down

Interview Tips

  • Explain exponential backoff with jitter
  • Discuss which errors are retryable vs permanent
  • Mention thundering herd problem
  • Combine with circuit breaker pattern
  • Know retry budgets for system-wide protection

Finished reading?

Mark it done to track your progress in Concepts.