Exponential Backoff in Axios Retry Logic

Exponential backoff is a standard error-handling strategy that progressively increases the wait time between consecutive retries for failed network requests. In Axios, implementing exponential backoff ensures that your application gracefully handles transient errors—such as network dropouts, rate limits (HTTP 429), or temporary server unavailability (HTTP 503)—without overwhelming the destination server. This guide covers how to incorporate exponential backoff into Axios using custom interceptors as well as pre-built community solutions.

The Mathematics of Exponential Backoff

The core concept relies on multiplying the delay duration exponentially based on the retry attempt number:

\[\text{Delay} = \text{Base Delay} \times 2^{\text{retryCount}} + \text{Jitter}\]

Adding "jitter" (a small, randomized amount of time) prevents the "thundering herd" problem, where multiple client instances retry failed requests at the exact same millisecond.


Method 1: Implementing via Native Axios Interceptors

You can build a lightweight exponential backoff mechanism directly within Axios using response interceptors and standard JavaScript promises without external dependencies.

import axios from 'axios';

const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
});

// Helper function to create a promise-based delay with jitter
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

apiClient.interceptors.response.use(null, async (error) => {
  const { config, response } = error;

  // Initialize retry count tracking
  config.__retryCount = config.__retryCount || 0;
  const maxRetries = 3;
  const baseDelay = 1000; // 1 second

  // Determine if error is retryable (Network error, 429, or 5xx server errors)
  const isRetryable =
    !response || (response.status >= 500 && response.status <= 599) || response.status === 429;

  if (isRetryable && config.__retryCount < maxRetries) {
    config.__retryCount += 1;

    // Calculate delay: baseDelay * 2^(retryCount - 1) + randomized jitter
    const exponentialDelay = baseDelay * Math.pow(2, config.__retryCount - 1);
    const jitter = Math.random() * 200; // 0-200ms randomized offset
    const totalDelay = exponentialDelay + jitter;

    await wait(totalDelay);

    // Re-execute the request with the updated config
    return apiClient(config);
  }

  return Promise.reject(error);
});

export default apiClient;

Method 2: Using the axios-retry Plugin

For a production-ready and configurable implementation, the axios-retry package provides built-in exponential backoff functionality.

Installation

npm install axios-retry

Configuration

import axios from 'axios';
import axiosRetry from 'axios-retry';

const apiClient = axios.create({
  baseURL: 'https://api.example.com',
});

axiosRetry(apiClient, {
  retries: 4, // Number of retry attempts
  retryDelay: (retryCount) => {
    // Built-in exponential backoff generator with jitter
    return axiosRetry.exponentialDelay(retryCount);
  },
  retryCondition: (error) => {
    // Retry on network errors or 5xx idempotent requests
    return (
      axiosRetry.isNetworkOrIdempotentRequestError(error) ||
      error.response?.status === 429
    );
  },
  shouldResetTimeout: true, // Reset request timeout between attempts
});

export default apiClient;

Best Practices for Axios Retry Logic

  1. Limit Retries to Idempotent Methods: By default, only retry safe or idempotent HTTP methods (GET, PUT, DELETE, HEAD, OPTIONS). Retrying non-idempotent methods like POST can result in duplicate transactions unless your server supports idempotency keys.
  2. Cap Maximum Delay: Ensure an upper boundary (e.g., maximum 30 seconds) so that requests do not hang indefinitely during severe outages.
  3. Respect Retry-After Headers: If a server returns an HTTP 429 or 503 status code with a Retry-After header, use that explicit delay duration instead of the calculated exponential backoff value.