arrow_left_alt Back to Blog

2Minutes: Retry Logic In JavaScript

calendar_today July 31, 2026 | timer 3 min read |

We’ve already talked about how a fetch() call can resolve successfully even when the server responds with a 500. But what do we actually do once we catch that failure? Most apps do the worst possible thing: give up after the first error and show the user a broken page.

What is Retry Logic?


Retry logic is exactly what it sounds like: instead of failing immediately, we attempt the same operation again, a few times, before finally giving up.

async function fetchWithRetry(url, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`Status ${response.status}`);
      return await response.json();
    } catch (error) {
      console.log(`Attempt ${attempt} failed: ${error.message}`);
      if (attempt === maxAttempts) throw error;
    }
  }
}

This works, but run it against a struggling server and you’ll notice something ugly: all three attempts fire back-to-back, milliseconds apart. We’re not giving the server any room to recover — we’re just hammering it three times instead of once.

The Backoff Fix


This is where exponential backoff comes in. Instead of retrying immediately, we wait a little longer after each failed attempt, doubling the delay every time.

async function fetchWithRetry(url, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`Status ${response.status}`);
      return await response.json();
    } catch (error) {
      if (attempt === maxAttempts) throw error;
      const delay = 2 ** attempt * 100; // 200ms, 400ms, 800ms...
      console.log(`Attempt ${attempt} failed, retrying in ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Now the second attempt waits 200ms, the third waits 400ms, and so on. The server gets breathing room, and — just as important — we’re not freezing anything while we wait, since a setTimeout wrapped in a Promise still hands control back to the Call Stack instead of blocking it.

The takeaway for today is that retry logic isn’t just “try again” — without a growing delay between attempts, you can turn one struggling server into a server getting hit three times as hard.

That’s 2Minutes Concept, see you in the next one!

Sources


https://advancedweb.hu/how-to-implement-an-exponential-backoff-retry-strategy-in-javascript

https://bpaulino.com/entries/retrying-api-calls-with-exponential-backoff

index.php