The Retry Pattern in Python: How Professionals Handle Failure
Banner Image

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:

  • Junior code crashes
  • Professional code recovers

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:

  • API calls
  • database queries
  • message queues
  • cloud services
  • microservices communication

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:

  • infinite loop
  • no backoff
  • hides real failures
  • can DDoS your own system

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:

  • prevents request storms
  • gives systems time to recover
  • plays nicely with rate limits

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:

  • validation errors
  • auth failures
  • logical bugs

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:

  • backoff
  • jitter
  • logging hooks
  • retry conditions
  • time limits

When NOT to Retry

Retries are not magic.

Don’t retry:

  • authentication failures
  • invalid input
  • business logic errors
  • idempotent-unsafe operations

Always ask:

“Will retrying actually help?”

Retry Pattern + Other Patterns

Retry works best when combined with:

  • Circuit Breaker
  • Timeouts
  • Idempotency
  • Rate limiting
  • Observability

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.

To view or add a comment, sign in

More articles by Uman Sheikh

Explore content categories