~learn.
Code lab · World 02 · Failure-First Design

Retry with exponential backoff and full jitter.

Retry only what can succeed later, wait base × 2^attempt capped, pick a random point below it, and honour Retry-After.

Easy~25 min2 example + 10 hidden tests+60 XPJump to the code ↓
Saving also stores the Python runtime (~13.5 MB, once for every lab) so this lab runs with no internet.

Ten servers that retry a failed call immediately, three times each, turn one blip into a stampede on a service that is already struggling (Week 2 preview, Concept 3). The fixes: wait longer after each failure (exponential backoff), add randomness so clients don't retry in lockstep (jitter), give up after a budget, and don't retry what can't succeed.

This lab uses full jitter: instead of adding a small random amount to the backoff, pick the whole delay at random below it.

text2 lines
ceiling = min(cap, base * 2 ** attempt)     # attempt = 0 for the first retry
delay   = rng.uniform(0, ceiling)

Implement two functions:

Function Does
backoff_delay(attempt, base, cap, rng) the full-jitter delay for retry number attempt (0-based)
retry(operation, *, max_attempts, base, cap, sleep, rng) calls operation() up to max_attempts times in total and returns its first result. Between attempts it calls sleep(delay)

Rules the tests check:

  • Retry only TransientError (and its subclass RateLimited), TimeoutError and ConnectionError. Anything else, such as PermanentError for a 4xx, is re-raised at once with no sleep (Day 4, section 3.2).
  • RateLimited carries retry_after seconds from the server's Retry-After header: sleep exactly that long instead of the backoff (the 429 exception in section 3.2).
  • After the last attempt fails, re-raise that exception, not a new one.
  • Never sleep for real: call the injected sleep. Draw randomness only from the injected rng (rng.uniform(a, b) or rng.random()), so the tests can predict every delay.
Python 3.14 loads on first run (~6.5 MB once)14
Loading the editor…
not run yet · 8 s limit
press Run tests

Write your code, then run 2 example tests and 10 hidden tests. Each run is limited to 8 seconds: an infinite loop gets stopped and Python restarts, your code stays.