All posts
·5 min read

Retry and exponential backoff patterns that work

How to retry failed API calls and background jobs without making things worse — exponential backoff, jitter, and circuit breakers explained.

retriesreliabilitypatterns

Retries are dangerous

A retry without backoff is a denial-of-service attack on the service you depend on. A retry without a cap is an infinite loop. A retry on a non-idempotent operation is a duplicate charge.

Get this right.

The pattern

Exponential backoff with jitter, capped retries, only on retriable errors.

That sentence contains four decisions. All four matter.

Exponential backoff

Wait 1s, then 2s, then 4s, then 8s. This gives the downstream service time to recover. Linear backoff (1s, 2s, 3s, 4s) does not give enough breathing room when something is genuinely down.

Jitter

Without jitter, every client retries at the same moment and hits the recovering service simultaneously. You created a thundering herd.

Add randomness: wait base * 2^attempt * random(0.5, 1.5). Now retries spread across time.

Capped retries

Three to five attempts is usually right. After that, accept the failure, log it, and move on. Infinite retries hide bugs and burn money.

Only on retriable errors

  • Retry: network timeouts, 502/503/504, 429 (with Retry-After)
  • Do not retry: 400, 401, 403, 404, 422 (these are bugs, not transient failures)

A 401 means your token is bad. Retrying will not fix it. Surface the error, fix the auth.

In Python

from tenacity import retry, stop_after_attempt, wait_exponential_jitter, retry_if_exception_type

@retry(
    stop=stop_after_attempt(5),
    wait=wait_exponential_jitter(initial=1, max=30),
    retry=retry_if_exception_type(httpx.HTTPStatusError),
)
async def fetch_user(user_id: str):
    ...

tenacity handles all of this. Use it.

In Celery

@app.task(
    autoretry_for=(httpx.HTTPStatusError,),
    retry_backoff=True,
    retry_backoff_max=600,
    retry_jitter=True,
    max_retries=5,
)
def sync_to_crm(record_id):
    ...

Built in. Use it.

Circuit breakers

If the downstream is consistently failing, stop hammering it. Open a circuit breaker after N failures, wait, then probe with a single request. Libraries like circuitbreaker handle this. Add one when you have a flaky third-party. Skip it for first-party calls.

Got a workflow problem?

Let's talk about whether n8n, a custom backend, or a hybrid fits your case.

A 30-minute discovery call. Free, honest, you leave with a written direction either way.

Start QuizBook a Call