APIs and Async · HTTP and JSON · lesson 5 of 12
Error handling and retries
about 16 minutes · free · runs in your browser
Trying again, but not forever
Networks fail transiently. A client that gives up on the first 503 is fragile; a client that retries forever is worse, because it turns a small outage into a stampede.
The shape worth learning:
for attempt in range(max_attempts):
response = requests.get(url)
if response.ok:
return response.json()
if not should_retry(response.status_code):
break # a 404 will not fix itself
time.sleep(backoff * (2 ** attempt)) # exponential backoff
raise RuntimeError("gave up")
Three ideas in there, all load-bearing:
- Only retry what might succeed. Retrying a 4xx wastes everyone's time.
- Back off exponentially. Waiting 1s, 2s, 4s gives the server room to recover; hammering every 100ms does not.
- Have a limit. Unbounded retries are an outage amplifier.
The fake /flaky endpoint fails twice and then succeeds, so a retry loop is genuinely
necessary.
Your turn: write fetch_with_retry(url, max_attempts=5) returning the parsed body,
retrying only on retryable statuses, and returning None if it never succeeds.
You start from this, and edit it in the browser:
import fake_requests as requests
def should_retry(status):
return status == 429 or 500 <= status < 600
def fetch_with_retry(url, max_attempts=5):
# Retry the retryable failures, give up on the rest.
pass