Axios Exponential Backoff with Full Jitter
Implementing exponential backoff with full jitter in Axios improves application resilience and prevents the "thundering herd" problem by randomizing and spacing out retry intervals for failed requests. This guide demonstrates how to configure Axios interceptors to detect transient network and server errors, calculate exponential delays with full jitter, and automatically re-execute failed HTTP requests safely.
The Mathematics of Full Jitter
Standard exponential backoff multiplies the base delay by \(2^n\) (where \(n\) is the retry attempt). However, if many clients fail at the same time, they will all retry at identical intervals. "Full Jitter" mitigates this by selecting a uniform random value between zero and the calculated exponential backoff ceiling:
\[\text{Delay}_{\text{max}} = \min(\text{MaxDelay}, \text{BaseDelay} \times 2^{\text{retryCount}})\] \[\text{SleepTime} = \text{random}(0, \text{Delay}_{\text{max}})\]
Implementation with Axios Interceptors
Axios interceptors allow you to intercept HTTP responses and retry the original request configuration before returning an error to the caller.
import axios from 'axios';
// 1. Define backoff settings
const RETRY_CONFIG = {
maxRetries: 4,
baseDelayMs: 1000,
maxDelayMs: 16000,
retryableStatuses: [408, 429, 500, 502, 503, 504],
};
// 2. Full Jitter Calculation
function calculateFullJitter(attempt, baseDelay, maxDelay) {
const exponentialBackoff = Math.min(maxDelay, baseDelay * Math.pow(2, attempt));
// Random delay between 0 and exponentialBackoff
return Math.floor(Math.random() * exponentialBackoff);
}
// 3. Create the Axios instance
const apiClient = axios.create({
timeout: 10000,
});
// 4. Attach the response interceptor
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const { config, response } = error;
// Do not retry if request config is missing
if (!config) {
return Promise.reject(error);
}
// Initialize the retry count
config.__retryCount = config.__retryCount || 0;
// Determine if the error is transient and retryable
const isNetworkError = !response && error.code !== 'ECONNABORTED';
const isRetryableStatus = response && RETRY_CONFIG.retryableStatuses.includes(response.status);
const canRetry = config.__retryCount < RETRY_CONFIG.maxRetries;
if ((isNetworkError || isRetryableStatus) && canRetry) {
config.__retryCount += 1;
const delay = calculateFullJitter(
config.__retryCount,
RETRY_CONFIG.baseDelayMs,
RETRY_CONFIG.maxDelayMs
);
// Wait for the calculated jitter delay
await new Promise((resolve) => setTimeout(resolve, delay));
// Retry the request
return apiClient(config);
}
return Promise.reject(error);
}
);
export default apiClient;Key Considerations
- Idempotency: By default, only safe or idempotent
HTTP methods (
GET,HEAD,PUT,DELETE) should be retried automatically. Retrying non-idempotent operations likePOSTwithout transaction identifiers or idempotency keys risks duplicate actions on the server. - Transient Status Codes: Focus retries on network
dropouts, rate-limiting (
429 Too Many Requests), and server-side errors (5xx). Do not retry standard client errors like400 Bad Requestor401 Unauthorized. - Request Timeout Interactions: Ensure the Axios request timeout is independent of the backoff sleep delay so retries are not prematurely aborted by client timers.