Integration Patterns

Retry With Exponential Backoff

Retry transient failures, but space attempts out so you recover instead of making things worse.

What you'll learn

  • Identify which failures are safe to retry
  • Implement increasing delays between attempts
  • Add jitter to avoid synchronised retry storms
  • Cap retries so failures surface rather than hang
  • Combine backoff with idempotency for safety

6 min

Why retry at all

Networks drop packets, services restart, and brief overloads happen. Many failures are transient — the same request would succeed if tried again a moment later. Retrying turns a momentary blip into a non-event for your users.

But not every failure should be retried. A 400 Bad Request means your request is malformed; sending it again changes nothing. A 401 means your credentials are wrong. Retrying these wastes effort and hides real bugs. Retry the failures that are plausibly temporary: connection timeouts, 429 rate-limit responses, and 5xx server errors. Treat client errors in the 4xx range (other than 429) as final.

Why exponential

A fixed delay can make congestion worse: if a service is struggling and every client retries after one second, you hammer it with synchronised waves. Exponential backoff doubles the wait after each attempt — 1s, 2s, 4s, 8s — so pressure eases as the service recovers.

The growth gives a struggling system breathing room while still recovering quickly from a one-off glitch. Pair it with a sensible ceiling so the delay does not balloon to minutes. The combination of retry quickly at first, then back right off is what makes the pattern resilient rather than aggressive.

Add jitter

Exponential backoff alone still synchronises clients that failed at the same instant — they all wait 1s, then 2s, then 4s together, producing repeating spikes. Jitter adds a small random offset so attempts spread out across time.

base = 1.0
delay = min(cap, base * (2 ** attempt))
delay = delay * random.uniform(0.5, 1.0)

The randomness smooths the load into a steady trickle instead of sharp peaks. This matters most when many clients share a backend, which is the common case for a public API. Without jitter, your retries can become a self-inflicted denial of service.

Bound the loop

Retries must end. Set a maximum attempt count or an overall deadline so a permanently broken dependency does not trap a request forever. When the budget is exhausted, fail clearly and let the caller decide what to do.

  • Max attempts — typically three to five for interactive calls.
  • Overall deadline — stop once total elapsed time exceeds a threshold.
  • Respect server hints — honour a Retry-After header when present.

Crucially, only retry operations that are safe to repeat. If a request might create or charge something twice, combine retries with idempotency so a duplicate has no extra effect.

Key takeaways

  • Retry transient failures: timeouts, 429s and 5xx responses
  • Do not retry most 4xx client errors — they will fail again
  • Grow the delay exponentially to ease pressure on a recovering service
  • Add random jitter so clients do not retry in synchronised waves
  • Cap attempts and honour Retry-After so failures eventually surface

FAQ

How many retries are sensible?

Three to five attempts covers most transient faults for interactive calls. Background jobs can afford more. The real limit is an overall deadline so a request never hangs indefinitely on a broken dependency.

What is jitter and why does it matter?

Jitter is a small random offset added to each backoff delay. It stops many clients that failed simultaneously from retrying in lockstep, which would create repeating load spikes on the service you are trying to reach.

Is it ever unsafe to retry?

Yes. Retrying a request that creates or modifies data can duplicate the effect. Make such operations idempotent first, then retries become safe because a repeated request produces the same single outcome.

Integrate with Merion

Ready to build?

Read the API reference, grab the OpenAPI spec, and ship a resilient integration.