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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Exponential backoff multiplies the delay each attempt so a struggling service gets progressively more breathing room.
- 2Jitter randomizes delays so concurrent clients don't all retry in lockstep and re-overload the target.
- 3A pluggable shouldRetry predicate lets callers bail out early on errors that won't fix themselves, like a 404.
Related explainers
typescript
import { CallHandler, ExecutionContext, Injectable,
Wrapping responses in a NestJS interceptor
interceptors
rxjs
response-shaping
Intermediate
7 steps
go
package main import ( "errors"
Parsing and validating CLI flags in Go
cli-parsing
validation
error-handling
Intermediate
8 steps
java
public class ThumbnailProcessor { private static final int MAX_CONCURRENCY = 4;
Bounded parallel thumbnail rendering in Java
concurrency
thread-pool
futures
Intermediate
7 steps
typescript
type RetryOptions = { retries?: number; timeoutMs?: number; baseDelayMs?: number;
Retry with timeout and backoff in TypeScript
promises
retry
exponential-backoff
Intermediate
10 steps
typescript
import { Pipe, PipeTransform, ChangeDetectorRef, NgZone, OnDestroy } from '@angular/core'; @Pipe({ name: 'timeAgo',
A self-refreshing timeAgo pipe in Angular
impure-pipe
change-detection
timers
Advanced
10 steps
go
package cache import ( "container/list"
Building a generic LRU cache in Go
lru-cache
generics
linked-list
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/retrying-async-tasks-with-exponential-backoff-explained-typescript-37ad/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.