Bound retry delays
Calls to a model or a tool sometimes fail for a moment: the service is busy, or the network drops. Trying again often works. But trying again at once, and for ever, makes a busy service busier. Exponential backoff is the polite way to retry: wait a little, then twice as long, then twice as long again. Two limits keep it sane. A cap stops the wait from growing without end, and a retry budget says when to give up.
Learn it
Words to know
- retry
- Trying a failed call again, in the hope that the failure was brief.
- exponential backoff
- Waiting longer before each retry, usually twice as long as the time before.
- retry budget
- The most retries allowed before the caller gives up and reports the failure.
How it works
- Attempts are numbered from 0. If
attemptismaxRetriesor more, the budget is used up: return -1, which means give up. - Otherwise the wait doubles with every attempt:
baseMs * 2 ** attempt. For a base of 100 that is 100, 200, 400, 800. - Never wait longer than
capMs: return the smaller of the two.
Worked example
Input attempt = 3 baseMs = 100 capMs = 500 maxRetries = 5 Output 500
| Attempt | Doubled wait | After the cap of 500 | Answer |
|---|---|---|---|
| 0 | 100 | 100 | 100 |
| 1 | 200 | 200 | 200 |
| 2 | 400 | 400 | 400 |
| 3 | 800 | 500 | 500 |
| 4 | 1600 | 500 | 500 |
| 5 | not computed | not computed | -1: give up |
- Doubling only
- With the cap
The example asks for attempt 3: the doubled wait of 800 is over the cap, so the answer is 500. The other rows show the same settings for attempts 0, 1, 2, 4 and 5.
Why it matters
Every call to a model service needs this, and client libraries often do it for you. Real retry code adds a small random change to each wait, called jitter, and takes care not to repeat a write that succeeded.
Your turn
Return how long to wait before a retry, or -1 when it is time to give up.
attempt- which retry this is, counted from 0; a whole number
baseMs- the wait before the first retry, in milliseconds; a whole number of 1 or more
capMs- the longest wait allowed, in milliseconds
maxRetries- how many retries are allowed in total; with 3, attempts 0, 1 and 2 are allowed
- Returns
- The wait in milliseconds:
baseMsdoubledattempttimes, but at mostcapMs. IfattemptismaxRetriesor more, -1.
What JavaScript gives you
2 ** n- 2 to the power n
Math.min(a, b)- the smaller of two numbers
What Python gives you
2 ** n- 2 to the power n
min(a, b)- the smaller of two numbers
Example 1
Input attempt = 0 baseMs = 100 capMs = 1000 maxRetries = 3 Output 100
The first retry waits the base time: 100 doubled zero times.
Example 2
Input attempt = 3 baseMs = 100 capMs = 500 maxRetries = 5 Output 500
100 doubled three times is 800, which is over the cap of 500.
Example 3
Input attempt = 3 baseMs = 100 capMs = 1000 maxRetries = 3 Output -1
With 3 retries allowed, the attempts are 0, 1 and 2. Attempt 3 is one too many.
Example 4
Input attempt = 2 baseMs = 100 capMs = 1000 maxRetries = 3 Output 400
The last allowed attempt: 100 doubled twice.
The checks also try
- no retries allowed at all
- a cap below the base
- the cap cuts the third wait
- ten doublings
- one attempt later, still at the cap
- the first attempt past the budget
- an attempt well past the budget
- forty doublings, far over the cap
Hints
Each hint gives away a little more. Open one only when you need it.
Hint 1: Where to start
Two checks, in order: is there any budget left, and is the doubled wait above the cap?
Hint 2: The key step
The wait before the cap is the base multiplied by 2 to the power of the attempt. Attempt 0 gives the base itself, because 2 to the power 0 is 1.
Hint 3: The outline
If
attemptis at leastmaxRetries, return -1. Otherwise computebaseMstimes 2 to the powerattempt, and return that orcapMs, whichever is smaller.
Solution
From here on the page gives the method away. If you are still solving, go back to the hints.
Step by step
- Out of retries: give up. Attempts count from 0, so with 3 retries allowed the last valid attempt is 2. The test is at least, not greater than.
- The wait doubles with every attempt. Doubling backs off quickly. After a few failures the caller is waiting long enough for a busy service to recover.
- Never wait longer than the cap. Without a cap, attempt 20 with a base of 100 milliseconds would wait more than a day.
The same numbered steps are the comments in the solution.
JavaScript
function solve(attempt, baseMs, capMs, maxRetries) {
// 1. Out of retries: give up
if (attempt >= maxRetries) return -1;
// 2. The wait doubles with every attempt
const wait = baseMs * 2 ** attempt;
// 3. Never wait longer than the cap
return Math.min(capMs, wait);
}Python
def solve(attempt, baseMs, capMs, maxRetries):
# 1. Out of retries: give up
if attempt >= maxRetries:
return -1
# 2. The wait doubles with every attempt
wait = baseMs * 2**attempt
# 3. Never wait longer than the cap
return min(capMs, wait)
Watch out
maxRetriesis a count, not the last attempt's number. With 3 retries, attempt 3 is already one too many.- Apply the cap to the first attempt too. A base of 500 with a cap of 100 waits 100.
- Only retry failures that can pass. A timeout may succeed next time; a wrong password or an invalid request fails the same way every time.
Check yourself
Why does the wait grow instead of staying the same?
A service that is failing is often overloaded. Waiting longer each time gives it room to recover instead of adding to the load.
What are the cap and the retry budget for?
The cap keeps a single wait from growing without end. The budget ends the retrying, so the caller can report a failure.