Skip to content

Wait Strategies

Backoff provides several built-in wait strategies (generators) that determine how long to wait between retries.

Exponential (expo)

Exponential backoff doubles the wait time after each retry.

import backoff

@backoff.on_exception(backoff.expo, Exception)
def my_function():
    pass

Wait sequence (without jitter): 1s, 2s, 4s, 8s, 16s, 32s, ...

Parameters

  • base - Base wait time in seconds (default: 1)
  • factor - Multiplier for each iteration (default: 2)
  • max_value - Maximum wait time cap (default: None)

Examples

# Custom base and factor
@backoff.on_exception(
    backoff.expo,
    Exception,
    base=2,      # Start at 2 seconds
    factor=3,    # Triple each time
    max_value=60 # Cap at 60 seconds
)
def custom_expo():
    pass
# Wait sequence: 2s, 6s, 18s, 54s, 60s, 60s, ...

Best For

  • API rate limiting
  • Network requests
  • Database connections
  • Most general-purpose retries

Fibonacci (fibo)

Fibonacci backoff follows the Fibonacci sequence.

@backoff.on_exception(backoff.fibo, Exception)
def my_function():
    pass

Wait sequence (without jitter): 1s, 1s, 2s, 3s, 5s, 8s, 13s, 21s, ...

Parameters

  • max_value - Maximum wait time cap (default: None)

Examples

@backoff.on_exception(
    backoff.fibo,
    Exception,
    max_value=30  # Cap at 30 seconds
)
def fibo_with_cap():
    pass
# Wait sequence: 1s, 1s, 2s, 3s, 5s, 8s, 13s, 21s, 30s, 30s, ...

Best For

  • Gradual backoff when you want slower growth than exponential
  • Polling operations
  • Resource-constrained environments

Constant

Fixed wait time between all retries.

@backoff.on_exception(
    backoff.constant,
    Exception,
    interval=5  # Always wait 5 seconds
)
def my_function():
    pass

Wait sequence: 5s, 5s, 5s, 5s, ...

Parameters

  • interval - Wait time in seconds (default: 1)

Examples

# Poll every 10 seconds
@backoff.on_predicate(
    backoff.constant,
    interval=10,
    jitter=None,  # Disable jitter for exact intervals
    max_time=300
)
def poll_every_10_seconds():
    pass

Best For

  • Regular polling
  • Fixed-rate retry policies
  • Cases where jitter is disabled

Runtime

Dynamic wait time based on function return value or exception.

@backoff.on_predicate(
    backoff.runtime,
    predicate=lambda r: r.status_code == 429,
    value=lambda r: int(r.headers.get("Retry-After", 1))
)
def respect_retry_after():
    return requests.get(url)

Parameters

  • value - Function that extracts wait time from return value or exception

Examples

HTTP Retry-After Header

def get_retry_after(response):
    """Extract Retry-After from HTTP response"""
    if response.status_code == 429:
        retry_after = response.headers.get("Retry-After")
        if retry_after:
            return int(retry_after)
    return 1  # Default

@backoff.on_predicate(
    backoff.runtime,
    predicate=lambda r: r.status_code == 429,
    value=get_retry_after,
    jitter=None
)
def api_call():
    return requests.get(api_url)

Exception-based Wait Time

class RetryableError(Exception):
    def __init__(self, message, wait_seconds):
        super().__init__(message)
        self.wait_seconds = wait_seconds

@backoff.on_exception(
    backoff.runtime,
    RetryableError,
    value=lambda e: e.wait_seconds
)
def custom_retry():
    raise RetryableError("Try again", wait_seconds=30)

Best For

  • Respecting server-specified retry delays
  • Custom retry logic from application responses
  • API rate limiting with Retry-After headers

Jitter

All wait strategies support jitter to add randomness and prevent thundering herd problems.

full_jitter (default)

Uses AWS's Full Jitter algorithm - wait time is random between 0 and the calculated wait.

@backoff.on_exception(backoff.expo, Exception)
# Equivalent to:
@backoff.on_exception(backoff.expo, Exception, jitter=backoff.full_jitter)

For exponential backoff: actual wait is random between 0 and 2^n seconds.

random_jitter

Adds random milliseconds (0-1000ms) to the calculated wait time.

@backoff.on_exception(backoff.expo, Exception, jitter=backoff.random_jitter)

Custom Jitter

import random

def custom_jitter(value):
    """Add 10-50% randomness"""
    jitter_amount = value * random.uniform(0.1, 0.5)
    return value + jitter_amount

@backoff.on_exception(backoff.expo, Exception, jitter=custom_jitter)
def my_function():
    pass

Disable Jitter

@backoff.on_exception(backoff.expo, Exception, jitter=None)

Comparison

Strategy Growth Rate Use Case Example Sequence (5 iterations)
expo Fast Network, APIs 1s, 2s, 4s, 8s, 16s
fibo Medium Polling 1s, 1s, 2s, 3s, 5s
constant None Fixed intervals 5s, 5s, 5s, 5s, 5s
runtime Variable Server-directed Depends on response

Choosing a Strategy

Use exponential when:

  • You want fast backoff for transient failures
  • Dealing with network or API calls
  • Following industry best practices

Use fibonacci when:

  • You want gentler backoff than exponential
  • Resource constraints matter
  • Polling for long-running operations

Use constant when:

  • You need predictable, fixed intervals
  • Polling at specific rates
  • Testing or debugging

Use runtime when:

  • Server tells you how long to wait
  • Retry delay is in the response/exception
  • Implementing Retry-After headers