python 41 lines · 6 steps

A retry decorator with exponential backoff

A configurable decorator that re-runs a flaky function, waiting longer between each failed attempt.

Explained by highlit
1import random
2import time
3import logging
4from functools import wraps
5 
6logger = logging.getLogger(__name__)
7 
8 
9def retry_with_backoff(
10 max_attempts=5,
11 base_delay=0.5,
12 max_delay=30.0,
13 backoff_factor=2.0,
14 jitter=True,
15 exceptions=(Exception,),
16):
17 def decorator(func):
18 @wraps(func)
19 def wrapper(*args, **kwargs):
20 delay = base_delay
21 for attempt in range(1, max_attempts + 1):
22 try:
23 return func(*args, **kwargs)
24 except exceptions as exc:
25 if attempt == max_attempts:
26 logger.error(
27 "%s failed after %d attempts: %s",
28 func.__name__, attempt, exc,
29 )
30 raise
31 sleep_for = min(delay, max_delay)
32 if jitter:
33 sleep_for = random.uniform(0, sleep_for)
34 logger.warning(
35 "%s attempt %d/%d failed (%s); retrying in %.2fs",
36 func.__name__, attempt, max_attempts, exc, sleep_for,
37 )
38 time.sleep(sleep_for)
39 delay *= backoff_factor
40 return wrapper
41 return decorator
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A three-layer nested function lets a decorator accept configuration arguments while still wrapping the target.
  2. 2Exponential backoff with a cap and jitter spreads out retries so a failing dependency isn't hammered in lockstep.
  3. 3Re-raising on the final attempt preserves the original exception so callers still see real failures.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A retry decorator with exponential backoff — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code