Coding problem · Medium · Async & Concurrency

Retry with Exponential Backoff

Medium Practical tasks Most askedSeen at Stripe, AWS, Netflix, Shopify

Implement retry(operation, maxAttempts, baseDelayMs, sleep):

  • Call operation. If it succeeds, return its result.
  • If it throws a retryable error and attempts remain, call sleep(delay) and try again. The delay before retry n (1-based) is baseDelayMs × 2^(n−1): 100, 200, 400, …
  • After maxAttempts total attempts, rethrow the last error.
  • Errors of type NonRetryableError (NonRetryableException in Java) are rethrown immediately, without sleeping.

sleep is injected so tests run instantly and can assert the delays. In JavaScript/TypeScript, operation and sleep may return promises — retry is async.

Example 1

Input: operation fails twice then returns "ok"; maxAttempts = 5, baseDelayMs = 100
Output: "ok", with sleeps [100, 200]

Hints

Hint 1

Loop attempt = 1..maxAttempts; on error decide: non-retryable → throw, last attempt → throw, else sleep and continue.

Hint 2

Production retries also add jitter (randomize delays) to avoid synchronized retry storms.

Write and run your solution

Loading the editor… (JavaScript is required to run code in your browser.)

Solution and explanation

Show the solution

Time O(maxAttempts) calls · Space O(1)

Loop over attempts; on failure, decide in this order: non-retryable → rethrow; out of attempts → rethrow the last error; otherwise sleep base × 2^(attempt−1) and loop. Injecting sleep (and in real code, a clock and a random source for jitter) keeps the function deterministic and testable.

In production also: retry only idempotent operations (or use idempotency keys), cap the maximum delay, add full jitter (random(0, delay)), respect Retry-After, and bound total retries across a call chain (retry budgets) to avoid amplification.

Reference solution

class NonRetryableError extends Error {}

async function retry(operation, maxAttempts, baseDelayMs, sleep) {
  for (let attempt = 1; ; attempt++) {
    try {
      return await operation();
    } catch (err) {
      if (err instanceof NonRetryableError || attempt >= maxAttempts) throw err;
      await sleep(baseDelayMs * 2 ** (attempt - 1));
    }
  }
}

Common mistakes

  • Retrying non-idempotent writes without an idempotency key (double charges).
  • Sleeping after the final attempt.
  • Swallowing the error and returning undefined/None.

Follow-up questions

  • Add full jitter and a max delay cap.
  • Respect a server's Retry-After header.
  • Wrap it as a decorator (Python) or higher-order function.