Introduction
This utility is an evolution of the Exponential Backoff pattern. While standard backoff helps, synchronized retries from thousands of clients can create a “Thundering Herd” effect—crashing a server just as it attempts to recover.
Adding Jitter (randomness) spreads these requests out, ensuring a smoother recovery for your backend.
The Jitter Utility
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Retries an async function with Full Jitter.
* @param {Function} fn - The async function to retry.
* @param {Object} options - Configuration options.
* @param {number} options.retries - Max number of retry attempts.
* @param {number} options.delay - Initial delay in ms.
* @param {number} options.maxDelay - Maximum allowable delay cap.
*/
async function retryWithJitter(fn, { retries = 3, delay = 1000, maxDelay = 30000 } = {}) {
try {
return await fn();
} catch (e) {
if (retries === 0) throw e;
// Use the current delay limit, but don't exceed maxDelay
const currentRange = Math.min(delay, maxDelay);
// Full Jitter: randomize between 0 and the current capped delay
const jitteredDelay = Math.random() * currentRange;
console.log(`Retrying in ${Math.round(jitteredDelay)}ms...`);
await new Promise(resolve => setTimeout(resolve, jitteredDelay));
return retryWithJitter(fn, { retries: retries - 1, delay: delay * 2, maxDelay });
}
}
Why Use Jitter?
| Feature | Exponential Backoff | Full Jitter |
|---|---|---|
| Logic | Predictable (1s, 2s, 4s…) | Randomized (0 to max) |
| Server Load | Spike-prone | Evenly distributed |
| Best For | Internal tools / CLI | Public APIs / Microservices |
How It Works
Wait Calculation: Instead of waiting exactly
Nms, we pick a random number between0andN.Decoherence: This ensures that multiple failing clients do not retry at the same time.
Efficiency: “Full Jitter” is widely considered the most effective way to reduce contention on a recovering resource.
Example Usage
1
2
3
4
5
6
7
8
9
10
const saveUserData = async (data) => {
console.log("📡 Requesting API...");
if (Math.random() > 0.5) return { success: true };
throw new Error("Connection Timeout");
};
// Optimal for high-traffic environments
retryWithJitter(saveUserData, { retries: 5, delay: 1000 })
.then(() => console.log("Success!"))
.catch(() => console.error("Failed after multiple attempts."));
Key Takeaway
For production-scale applications, always favor Jitter. It transforms a fragile retry loop into a robust recovery mechanism that respects the health of your entire system architecture.
Changelog
- — Initial publication with Full Jitter implementation.