typescript 85 lines · 3 tabs

Angular HttpInterceptor With Exponential Backoff and Jittered Retries

Shared by codesnips Jul 2026
3 tabs
import { ApplicationConfig } from '@angular/core';
import {
  provideHttpClient,
  withInterceptors,
} from '@angular/common/http';
import { retryInterceptor } from './retry.interceptor';
import { RETRY_CONFIG } from './backoff';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([retryInterceptor])),
    {
      provide: RETRY_CONFIG,
      useValue: {
        maxRetries: 4,
        baseDelayMs: 250,
        maxDelayMs: 10000,
        retryableStatuses: [429, 500, 502, 503, 504],
      },
    },
  ],
};
3 files · typescript Explain with highlit

This snippet shows a production-ready HTTP resilience layer for Angular built around a functional HttpInterceptorFn and a small, testable backoff policy. The core idea is that transient failures — network blips, 503s during a deploy, rate-limit 429s — should not surface to the caller on the first attempt. Instead the interceptor transparently re-issues the request a bounded number of times, spacing attempts out with exponential backoff so the server gets breathing room rather than a thundering herd.

In backoff.ts, the policy is isolated from Angular entirely so it can be unit tested in isolation. computeDelay doubles a base delay per attempt (base * 2^attempt), caps it at maxDelayMs, and then applies full jitter with Math.random(). Jitter matters: without it, many clients that failed at the same instant retry in lockstep and re-collide. isRetryable centralises the decision of what deserves a retry — network errors (status === 0) plus a small allowlist of idempotent-friendly status codes — keeping that logic out of the RxJS pipeline.

In retry.interceptor.ts, the request is wrapped with RxJS retry, whose delay callback receives both the thrown error and the 1-based retry count. The callback re-checks isRetryable and the attempt ceiling: if either fails it rethrows via throwError, ending the stream with the original HttpErrorResponse so downstream error handlers behave normally. Otherwise it returns a timer observable, which retry subscribes to before re-running the source request. A guard skips non-GET verbs, since blindly replaying a POST risks duplicate side effects unless the endpoint is idempotent. The RETRY_CONFIG injection token lets each app override attempts and timing without editing the interceptor.

app.config.ts wires everything up with provideHttpClient(withInterceptors(...)), the modern standalone approach that replaces the old class-based HTTP_INTERCEPTORS multi-provider. The token is provided here so tests or feature modules can swap in aggressive or disabled policies. The main trade-off is latency versus reliability: retries hide failures but can delay a genuinely dead request by seconds, so the caps and the retryable allowlist keep worst-case delay bounded and avoid hammering endpoints that will never recover.


Related snips

Share this code

Here's the card — post it anywhere.

Angular HttpInterceptor With Exponential Backoff and Jittered Retries — share card
Link copied