advanced 7 min read
Retry, Exponential Backoff & Request Resilience
Retry with exponential backoff and jitter is the standard pattern for handling transient network failures. Combined with AbortController cancellation and idempotency, it creates resilient API layers.
Why it matters in interviews
Lead-level interviews test whether you retry blindly (dangerous for non-idempotent mutations) or intelligently (only 5xx/network errors, with backoff and jitter to prevent thundering herd). This separates production-aware engineers from tutorial-level knowledge.
Visual & Interactive Explanation
Interactive Flow Execution
1
Request
fetch(url)
2
Success?
2xx → return response
3
Retryable?
5xx or network error
4
Max retries?
exceeded → throw
5
Backoff
delay × 2^attempt + jitter
6
Retry
go to step 1
Code Examples & Implementation
Retry with Exponential Backoff & Jitter
async function fetchWithRetry(
url: string,
options: RequestInit = {},
maxRetries = 3,
baseDelay = 1000,
): Promise<Response> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.ok) return response;
// Only retry on server errors (5xx)
if (response.status < 500) throw new Error(`HTTP ${response.status}`);
// Respect Retry-After header
const retryAfter = response.headers.get("Retry-After");
if (retryAfter && attempt < maxRetries) {
await sleep(parseInt(retryAfter) * 1000);
continue;
}
} catch (err) {
if (attempt === maxRetries) throw err;
}
// Exponential backoff with jitter
const delay = baseDelay * Math.pow(2, attempt);
const jitter = delay * 0.5 * Math.random();
await sleep(delay + jitter);
}
throw new Error("Max retries exceeded");
}
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}AbortController — Request Cancellation & Timeout
async function fetchWithTimeout(
url: string,
timeoutMs = 5000,
): Promise<Response> {
const controller = new AbortController();
// Auto-abort after timeout
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
return response;
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") {
throw new Error(`Request timed out after ${timeoutMs}ms`);
}
throw err;
} finally {
clearTimeout(timeoutId);
}
}
// Modern alternative (Node 18+ / newer browsers):
// await fetch(url, { signal: AbortSignal.timeout(5000) });Idempotency Key for Safe Mutation Retries
async function createOrder(orderData: OrderInput) {
// Generate a unique idempotency key per user action
const idempotencyKey = crypto.randomUUID();
return fetchWithRetry("/api/orders", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(orderData),
});
// Server deduplicates: if same key arrives twice,
// returns the original response without creating a duplicate order
}Request Deduplication
const inflightRequests = new Map<string, Promise<any>>();
async function deduplicatedFetch<T>(url: string): Promise<T> {
// If an identical request is already in flight, reuse it
if (inflightRequests.has(url)) {
return inflightRequests.get(url)!;
}
const promise = fetch(url)
.then((r) => r.json())
.finally(() => inflightRequests.delete(url));
inflightRequests.set(url, promise);
return promise;
}
// Multiple components mounting simultaneously
// share a single network requestInterview-Ready Answers
Retry transient failures (5xx, network errors) with exponential backoff + jitter. Never blindly retry mutations — require idempotency keys.
Follow-up Questions & Deep Dives
🎯 Code Output — What Does This Print?
Question 1medium
const controller = new AbortController();
fetch("/api/data", { signal: controller.signal })
.then((r) => console.log("success"))
.catch((e) => console.log("error:", e.name));
controller.abort();Question 2hard
let attempts = 0;
async function retryFetch() {
for (let i = 0; i < 3; i++) {
attempts++;
try {
throw new Error("network error");
} catch (e) {
console.log("attempt", attempts);
if (i === 2) throw e;
}
}
}
retryFetch().catch((e) => console.log("failed after", attempts));Common Mistakes & Anti-Patterns
- Retrying all HTTP errors including 4xx client errors (they will always fail)
- No jitter in backoff, causing thundering herd during outages
- Retrying POST/DELETE mutations without idempotency keys, causing duplicate operations
- No maximum retry limit, causing infinite retry loops
- Not cancelling obsolete requests in search/autocomplete (race conditions)
Real-World Architectural Scenario
A payment service experiences intermittent 503 errors. Users click 'Pay' and sometimes get charged twice. How do you fix this?
Rate Your Readiness
Rate how comfortably you can explain this in an interview to update your global readiness gauge:
Rate your confidence: