typescript 46 lines · 8 steps

Retrying async tasks with exponential backoff

A generic helper that retries a failing promise, growing the wait between attempts and adding jitter to avoid thundering herds.

Explained by highlit
1interface RetryOptions {
2 retries?: number;
3 baseDelayMs?: number;
4 maxDelayMs?: number;
5 factor?: number;
6 jitter?: boolean;
7 shouldRetry?: (error: unknown, attempt: number) => boolean;
8}
9 
10const sleep = (ms: number): Promise<void> =>
11 new Promise((resolve) => setTimeout(resolve, ms));
12 
13async function retryWithBackoff<T>(
14 task: () => Promise<T>,
15 options: RetryOptions = {}
16): Promise<T> {
17 const {
18 retries = 5,
19 baseDelayMs = 100,
20 maxDelayMs = 10_000,
21 factor = 2,
22 jitter = true,
23 shouldRetry = () => true,
24 } = options;
25 
26 let attempt = 0;
27 
28 while (true) {
29 try {
30 return await task();
31 } catch (error) {
32 attempt++;
33 if (attempt > retries || !shouldRetry(error, attempt)) {
34 throw error;
35 }
36 
37 const exponential = baseDelayMs * Math.pow(factor, attempt - 1);
38 const capped = Math.min(exponential, maxDelayMs);
39 const delay = jitter ? Math.random() * capped : capped;
40 
41 await sleep(delay);
42 }
43 }
44}
45 
46export { retryWithBackoff, RetryOptions };
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Exponential backoff multiplies the delay each attempt so a struggling service gets progressively more breathing room.
  2. 2Jitter randomizes delays so concurrent clients don't all retry in lockstep and re-overload the target.
  3. 3A pluggable shouldRetry predicate lets callers bail out early on errors that won't fix themselves, like a 404.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Retrying async tasks with exponential backoff — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code