Back to examples
Error Handling

Exponential Backoff

Retry with exponentially increasing delays between attempts.

When to use

Use this when calling rate-limited APIs or services that need progressively longer recovery windows.

error-handling/exponential-backoff.py
"""
02_exponential_backoff.py: Retry with exponentially increasing delays.

Demonstrates:
- retry_delay_seconds=[0.01, 0.05, 0.1]  (list form)
- Each retry uses the next delay in the list; the last value is reused if retries exceed list length
"""

from dagy import flow, task

_calls: dict[str, int] = {}


@task(retries=3, retry_delay_seconds=[0.01, 0.05, 0.1])
def unstable_api(endpoint: str) -> dict:
    """Simulates an API that is unavailable for the first two calls."""
    _calls[endpoint] = _calls.get(endpoint, 0) + 1
    call = _calls[endpoint]
    print(f"  unstable_api({endpoint!r}) call #{call}")
    if call < 3:
        raise ConnectionError(f"Service unavailable (call #{call})")
    return {"endpoint": endpoint, "data": "OK", "call": call}


@task
def handle_response(response: dict) -> str:
    return f"Got response from {response['endpoint']} on call #{response['call']}"


@flow(name="exponential_backoff_flow")
def exponential_backoff_flow(endpoint: str = "/api/data") -> None:
    response = unstable_api(endpoint)
    handle_response(response)


if __name__ == "__main__":
    result = exponential_backoff_flow.run_local(endpoint="/api/data")
    print(f"Run ID : {result.run_id}")
    print(f"Status : {result.status}")

How it works

  1. `retry_delay_seconds=[0.01, 0.05, 0.1]` specifies increasing delays for each retry.
  2. If retries exceed the list length, the last value is reused.
  3. `unstable_api` simulates a service that is unavailable for the first two calls.
  4. On the third call it succeeds, and `handle_response` formats the result.

Inputs

  • `endpoint` (str, default `"/api/data"`): API endpoint to call.

Outputs

  • A formatted response string.

Dependencies

  • `dagy`