Write a retry function with exponential backoff for an async call.
Loop up to a maximum number of attempts, awaiting the call in a try/catch. On failure, wait delay times 2 to the power of the attempt, then try again. After the last failure, throw the last error.
Explain it simply
A flaky network call, like a UPI payment status check, sometimes works on the second or third try. Retrying immediately can make things worse by hammering the server, so each wait is doubled: 100ms, 200ms, 400ms. That is exponential backoff.
You need a small sleep helper that returns a promise resolved by setTimeout. Then a loop: try the call, return on success, catch and wait on failure, and rethrow the last error once attempts run out.
In real systems you would also add a bit of random jitter, and only retry errors that make sense to retry, such as timeouts, not wrong-password errors.
Line by line
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function retry(fn, attempts = 3, delay = 100) {
let lastError;
for (let i = 1; i <= attempts; i++) {
try {
return await fn();
} catch (e) {
lastError = e;
if (i < attempts) await sleep(delay * 2 ** (i - 1));
}
}
throw lastError;
}
let calls = 0;
const flaky = async () => {
calls++;
if (calls < 3) throw new Error("fail " + calls);
return "ok after " + calls;
};
retry(flaky, 5, 10).then(console.log); // Output: ok after 3What to say in the interview
"I loop for a fixed number of attempts, await the function inside try/catch, and sleep for delay times two to the power of the attempt before the next try. If all attempts fail I throw the last error. I would add jitter and retry only transient failures."
Complexity
Time is bounded by the sum of the delays, O(2^attempts) in the worst case, plus the calls themselves. Space O(1).
Common mistakes
- Writing return fn() without await, so the catch never sees the rejection.
- Retrying forever without a maximum.