Exponential Backoff Schedule with Jitter Bounds
Problem Statement
A tool returns 429. The agent retries immediately, gets another 429, retries again - and a transient blip becomes a self-inflicted outage, with every concurrent agent hammering in lockstep. Exponential backoff spaces the retries out; jitter breaks the lockstep. Before shipping either, you want to know the actual numbers: how long the worst case takes, and when the growth flattens against the cap.
Background
For a 0-based attempt index , the nominal delay is
Jitter turns each fixed delay into a range. With a jitter fraction the actual sleep is drawn uniformly from
so is no jitter, is "equal jitter" and is "full jitter" (the range starts at zero). You are computing the deterministic bounds, not sampling - the schedule must be reproducible.
Your Task
Implement:
def backoff_schedule(base, factor, cap, attempts, jitter):
Return a dict:
- delays: list of [lo, hi] pairs, one per attempt, each rounded to 4 decimal places.
- best_case_total: sum of the (already rounded) lo values, rounded to 4 dp.
- worst_case_total: sum of the (already rounded) hi values, rounded to 4 dp.
- capped_at: the smallest 0-based attempt index whose uncapped delay base * factori** is >= cap, or -1 if the cap is never reached within attempts.
Input/Output Format
All floats are rounded to 4 decimal places. capped_at is an int. With attempts = 0, delays is empty and both totals are 0.0.
Sample
print(backoff_schedule(1.0, 2.0, 8.0, 5, 0.5)["delays"])
# [[0.5, 1.0], [1.0, 2.0], [2.0, 4.0], [4.0, 8.0], [4.0, 8.0]]
Delays double until they hit the 8.0 cap at attempt 3, and equal jitter puts the lower bound at half of each.
Example
print(backoff_schedule(1.0, 2.0, 8.0, 5, 0.5)['delays'])
[[0.5, 1.0], [1.0, 2.0], [2.0, 4.0], [4.0, 8.0], [4.0, 8.0]]
- Inputs:
bases is the first delay,factordoubles it each attempt,caps is the ceiling,attemptsasks for five delays (indices to ), andjitteris equal jitter. - Uncapped growth: gives seconds. Attempt is the first where this reaches the cap (), so the capped-at index is .
- Apply the cap: leaves alone and pulls down to . These are the upper bounds of each range.
- Lower bounds: gives . Pairing each lower bound with its yields
[[0.5, 1.0], [1.0, 2.0], [2.0, 4.0], [4.0, 8.0], [4.0, 8.0]]. - Totals: best case s, worst case s.
- Notice the last two pairs are identical: once the cap bites, extra attempts add a flat s each instead of doubling. Full jitter would make every lower bound .
Constraints
base > 0,factor >= 1.0,cap > 0,0 <= attempts <= 64,0 <= jitter <= 1- Attempt indices are 0-based: the first delay is
base * factor**0 = base - Every delay bound is rounded to 4 decimal places
- Totals are the sums of the already-rounded bounds, rounded again to 4 dp
capped_atcompares the uncapped delay againstcapusing>=- Never emit a raw unrounded float