The Retry Pattern in Python: How Professionals Handle Failure
If your Python code talks to the outside world, it will fail.
APIs time out. Networks glitch. Databases briefly go unavailable.
The difference between junior and senior code is simple:
That’s where the Retry Pattern comes in.
What Is the Retry Pattern?
The Retry Pattern means:
When an operation fails due to a temporary issue, retry it safely, intentionally, and with limits.
This pattern is critical for:
But retries done wrong can be worse than no retries at all.
The Wrong Way (What Most Code Looks Like)
while True:
try:
call_external_api()
break
except Exception:
pass
Problems:
This is not resilience. This is chaos.
The Professional Way: Controlled Retries
Let’s build it step by step.
1. Simple Retry With Limits
from typing import Callable, TypeVar
import time
T = TypeVar("T")
def retry(
func: Callable[[], T],
retries: int = 3,
delay: float = 1.0
) -> T:
for attempt in range(1, retries + 1):
try:
return func()
except Exception as exc:
if attempt == retries:
raise
time.sleep(delay)
Good start, but still incomplete.
2. Add Exponential Backoff (Production Grade)
import time
from typing import Callable, TypeVar
T = TypeVar("T")
def retry_with_backoff(
func: Callable[[], T],
retries: int = 3,
base_delay: float = 0.5
) -> T:
for attempt in range(1, retries + 1):
try:
return func()
except Exception:
if attempt == retries:
raise
sleep_time = base_delay * (2 ** (attempt - 1))
time.sleep(sleep_time)
Why backoff matters:
3. Retry Only What Makes Sense
Not all errors are retryable.
from typing import Tuple, Type
import time
def retry_on_exceptions(
func,
retry_for: Tuple[Type[Exception], ...],
retries: int = 3,
delay: float = 1.0
):
for attempt in range(retries):
try:
return func()
except retry_for:
if attempt == retries - 1:
raise
time.sleep(delay)
This avoids retrying:
4. Real-World Example: API Call
import requests
from requests.exceptions import Timeout, ConnectionError
from typing import Dict, Any
def fetch_data() -> Dict[str, Any]:
response = requests.get("https://api.example.com/data", timeout=2)
response.raise_for_status()
return response.json()
data = retry_on_exceptions(
fetch_data,
retry_for=(Timeout, ConnectionError),
retries=4,
delay=1.5
)
Clean. Predictable. Safe.
Use a Battle-Tested Library (Recommended)
In real production systems, don’t reinvent everything.
tenacity (industry favorite)
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=1, max=10),
)
def fetch_data() -> str:
...
Tenacity gives you:
When NOT to Retry
Retries are not magic.
Don’t retry:
Always ask:
“Will retrying actually help?”
Retry Pattern + Other Patterns
Retry works best when combined with:
Resilience is a system, not a function.
Final Thought
Failures are normal. Uncontrolled retries are dangerous.
Professional Python code doesn’t hope things work, it plans for failure.
Master the Retry Pattern, and your systems become resilient, stable and production-ready
— Uman Sheikh Helping developers write Python that survives production.