Loading…
Loading…
Retry delays, jitter and total wait, with copyable retry code
What retry 1 waits before jitter. Whole milliseconds.
2 doubles the delay each retry. 1 is constant backoff.
The cap: no single wait goes above this.
Retries after the first attempt.
Re-roll draws a new random sample for the jittered delays. The starting values are examples: the 32,000 ms cap is the maximum retry delay of the Google Cloud Storage Java client, and full jitter did the least client work in the AWS simulation (the post calls the choice between full and decorrelated "less clear").
This sample waits 26.1 s in total over 6 attempts; expected total 15.5 s.
| Retry | Sampled delay | Min – max | Expected | Cumulative wait |
|---|---|---|---|---|
| 1 | 627 ms | 0 ms – 1,000 ms | 500 ms | 627 ms |
| 2 | 5 ms | 0 ms – 2,000 ms | 1,000 ms | 633 ms |
| 3 | 2,110 ms (2.11 s) | 0 ms – 4,000 ms | 2,000 ms | 2,742 ms (2.74 s) |
| 4 | 7,848 ms (7.85 s) | 0 ms – 8,000 ms | 4,000 ms | 10,591 ms (10.6 s) |
| 5 | 15,494 ms (15.5 s) | 0 ms – 16,000 ms | 8,000 ms | 26,085 ms (26.1 s) |
RFC 9110 §10.2.3 allows delay-seconds or an HTTP-date.
// Exponential backoff: base 1000 ms, x2 per retry, cap 32000 ms, 5 retries, full jitter.
// Retries 408, 429 and 5xx responses plus network errors. Only retry requests that are safe to repeat (idempotent).
const BASE_MS = 1000;
const MULTIPLIER = 2;
const MAX_DELAY_MS = 32000;
const MAX_RETRIES = 5;
// Upper bound on a server's Retry-After: lower it to your caller's deadline.
const MAX_RETRY_AFTER_MS = 600000;
// retry starts at 1. Full jitter: random between 0 and the capped exponential.
function backoffDelay(retry) {
const exp = Math.min(MAX_DELAY_MS, BASE_MS * Math.pow(MULTIPLIER, retry - 1));
return Math.random() * exp;
}
function isRetryable(status) {
return status === 408 || status === 429 || status >= 500;
}
// The three HTTP-date shapes of RFC 9110 §5.6.7: IMF-fixdate, plus the obsolete
// RFC 850 and asctime forms that recipients must still accept.
const IMF_FIXDATE = /^(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d{2}:\d{2}:\d{2} GMT$/;
const RFC850_DATE = /^(Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day, (\d{2})-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d{2}) (\d{2}:\d{2}:\d{2}) GMT$/;
const ASCTIME_DATE = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([ \d]\d) (\d{2}:\d{2}:\d{2}) (\d{4})$/;
// Epoch ms for an HTTP-date, NaN for anything else. The obsolete forms are rewritten
// to IMF-fixdate first: it is the one shape ECMAScript guarantees Date.parse reads
// (Date#toUTCString output). Never hand Date.parse the raw header: it reads "1.5" as
// 5 Jan 2001 and "-1" as a month, which would turn a malformed value into an immediate retry.
function httpDateToMs(value) {
if (IMF_FIXDATE.test(value)) return Date.parse(value);
const rfc850 = RFC850_DATE.exec(value);
if (rfc850) {
const [, day, dd, mon, yy, time] = rfc850;
const parse = (year) => Date.parse(`${day.slice(0, 3)}, ${dd} ${mon} ${year} ${time} GMT`);
// Two-digit year: §5.6.7 reads an instant more than 50 years ahead as the past century.
const at = parse(2000 + Number(yy));
const horizon = new Date();
horizon.setUTCFullYear(horizon.getUTCFullYear() + 50);
return at > horizon.getTime() ? parse(1900 + Number(yy)) : at;
}
const asctime = ASCTIME_DATE.exec(value);
if (asctime) {
const [, day, mon, d, time, year] = asctime;
return Date.parse(`${day}, ${d.trim().padStart(2, '0')} ${mon} ${year} ${time} GMT`);
}
return Number.NaN;
}
// Retry-After (RFC 9110 §10.2.3) is delay-seconds (a non-negative decimal integer) or
// an HTTP-date. Anything else ("1.5", "-1", "1e3", an ISO timestamp) returns null so
// the computed backoff is used. Honored on every retried response, clamped to MAX_RETRY_AFTER_MS.
function retryAfterMs(response) {
const header = response.headers.get('retry-after');
if (header === null) return null;
const value = header.trim();
if (/^\d+$/.test(value)) return Math.min(MAX_RETRY_AFTER_MS, Number(value) * 1000);
const at = httpDateToMs(value);
return Number.isNaN(at) ? null : Math.min(MAX_RETRY_AFTER_MS, Math.max(0, at - Date.now()));
}
// init is reused for every attempt. Pass a function that builds a fresh init
// when the body is a stream: a ReadableStream can only be read once.
// Like fetch, an HTTP status never throws: once the retries are used up the last
// retryable response is returned so the caller can read its status, headers and
// body. Only a network failure on the final attempt throws.
async function fetchWithBackoff(url, init = {}) {
let lastError = new Error('fetchWithBackoff: no attempts made');
let lastResponse = null;
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
let response = null;
try {
response = await fetch(url, typeof init === 'function' ? init() : init);
if (!isRetryable(response.status)) return response;
lastResponse = response;
} catch (error) {
lastError = error; // network failure, DNS, timeout
lastResponse = null;
}
if (attempt === MAX_RETRIES) break;
const serverWait = response ? retryAfterMs(response) : null;
const waitMs = serverWait ?? backoffDelay(attempt + 1);
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
if (lastResponse !== null) return lastResponse;
throw lastError;
}
// Usage:
// const res = await fetchWithBackoff('https://api.example.com/items', { method: 'GET' });
// if (!res.ok) { /* retries exhausted (or a non-retryable status): res is the last response */ }
// With a streamed body, build a new init per attempt:
// const res = await fetchWithBackoff(url, () => ({ method: 'POST', body: makeStream() }));
The snippet retries 408, 429 and 5xx responses and network errors, and gives up after 5 retries. Only wrap requests that are safe to repeat.
n is the number of previous retries, so retry 1 uses n = 0 and waits the base delay (before jitter). The AWS simulator counts the failed attempt first, so its first retry sleeps 2 × base; the formulas are the same with n shifted by one. Every calculation runs in your browser.
Run your projects on Claude Code?
We packaged the .claude config that runs this site — 26 specialist agents, 14 workflow skills, and 6 rule files from 31 real production incidents. From $9.
Get the Claude Code Production Pack — $29Exponential backoff spaces retries further apart after each failure, so a struggling service is not hit again the moment it fails. The catch is that backoff alone keeps clients synchronized: every caller that failed in the same instant retries in the same instant. Marc Brooker's March 2015 post on the AWS Architecture Blog showed, with a simulation of contending clients, that adding jitter spreads those spikes and cuts both total work and completion time. This calculator turns a base delay, multiplier, cap and retry count into the exact schedule for each strategy, then writes the retry loop for you.
Each retry gets a capped exponential value, min(cap, base × multiplier^n), where n is the number of previous retries (0 for retry 1, so retry 1 waits the base). The AWS post uses a multiplier of 2; the field here accepts any factor from 1 to 10, so a multiplier of 1 shows a constant-delay policy for comparison. The value is computed by repeated multiplication that stops at the cap, so a very large retry count never overflows to Infinity, and a base of 0 stays 0.
The jitter mode decides how that value becomes the actual sleep. No jitter uses it as is. Full jitter draws random_between(0, value), so the range is 0 to the value and the mean is half. Equal jitter draws value / 2 + random_between(0, value / 2), so the range is half to the full value and the mean is three quarters. Decorrelated jitter ignores the attempt number: each sleep is min(cap, random_between(base, previous sleep × 3)), starting from the base. Its range for retry k (counting from 1) runs from the base to min(cap, base × 3^k), so retry 1 can wait anywhere from the base to three times the base, and because there is no closed form for its mean, the expected column averages 2,000 simulated runs.
The summary tiles add the columns up. Total wait is the sum of the sampled delays, the range is the sum of the per-retry minimums and maximums, the expected total is the sum of the means, and attempts is the retry count plus the first call. A "cap" badge marks every retry whose backoff has reached the max delay. Samples come from a seeded generator, so a given sample is reproducible, and "Re-roll jitter" draws the next one.
The code tab writes a retry wrapper from the same inputs: fetch with async/await in JavaScript and TypeScript, requests in Python, net/http in Go, and a curl loop in bash. Each one retries 408, 429 and 5xx responses and network errors, the set Google Cloud documents as transient, and gives up after the configured number of retries. With "Honor Retry-After" on, the wrapper parses the header as RFC 9110 defines it, delay-seconds or an HTTP-date, on any retried response and sleeps that long instead of the computed delay, capped at 10 minutes so a runaway value cannot stall the client.
A backend engineer wrapping calls to a payments API that returns 429 under load sizes the cap so five retries fit inside a 30-second request deadline, then copies the TypeScript wrapper.
An SRE reviewing an incident where a fleet of workers retried in lockstep after a cache restart switches the schedule from no jitter to full jitter and shows the team the range column.
A data engineer writing a Python ingestion job against a rate-limited API keeps Retry-After handling on so the job sleeps the server's value on 429 instead of guessing.
A platform team writing the retry policy for a Go service compares equal and decorrelated jitter against full jitter and documents the choice with the AWS results.
A developer scripting a nightly curl health check generates the bash loop so a single 503 during a deploy window does not page anyone.
Scope note: The schedule models your client's waits, not your system. It leaves out request latency, server processing time and timeouts, and it cannot tell you whether the service you call is safe to retry. The snippets are starting points: a production client also needs a total deadline, a circuit breaker and metrics on retry counts, and it should retry only idempotent operations. The AWS results come from a simulation of contending clients; measure your own system before committing to a policy.
Enter the base delay in milliseconds (what retry 1 waits before jitter), the multiplier (2 doubles the delay each retry) and the max delay cap
Set max retries. The schedule lists one row per retry, so attempts = retries + 1
Pick a jitter mode: none, full, equal or decorrelated. Jittered modes show a sampled delay, the min–max range and the expected value; click "Re-roll jitter" to draw a new sample
Read the total wait, the expected total and the worst case to check the policy fits the deadline of the caller
Open the code tab you need (JavaScript, TypeScript, Python, Go or curl/bash), keep "Honor Retry-After" on so the code waits the server's value on any retried response that carries the header, and copy the snippet
About the Exponential Backoff Calculator
A retry policy where the wait before each retry grows by a constant factor, up to a cap. The AWS Architecture Blog post "Exponential Backoff And Jitter" (Marc Brooker, March 4, 2015) describes capped exponential backoff as clients that "multiply their backoff by a constant after each attempt, up to some maximum value", and its simulator computes it as min(cap, base × 2^n). This calculator takes n as the number of previous retries, so retry 1 uses n = 0 and waits the base: with a 1,000 ms base, a multiplier of 2 and a 32,000 ms cap, the first five retries wait 1, 2, 4, 8 and 16 seconds, and every later retry waits the cap. The AWS simulator counts the failed attempt first, so its first retry sleeps 2 × base and the same inputs give 2, 4, 8, 16 and 32 seconds; the curve is identical, shifted by one step. The point is to stop hammering a service that just failed, while still finishing quickly when the failure was a blip.
Because backoff alone keeps clients synchronized. Every caller that failed in the same instant computes the same delay and retries in the same instant, so the load arrives in spikes with idle gaps between them. The AWS post puts it this way: with plain backoff "we've just introduced times when no client is competing", and "the solution isn't to remove backoff. It's to add jitter." Randomizing each delay spreads the retries out toward a steady rate. The post's conclusion: "The return on implementation complexity of using jittered backoff is huge."
Full jitter, unless you have a measured reason not to. The AWS post simulated contending clients against a shared resource and reports that equal jitter is "the loser": it does slightly more work than full jitter and takes much longer. Full jitter and decorrelated jitter were close: the post calls the decision between them "less clear", the "Full Jitter" approach "uses less work, but slightly more time", and decorrelated jitter made more calls. The post's own recommendation is jittered backoff in general ("it should be considered a standard approach for remote clients"), not one variant; full jitter is this tool's default because it did the least client work. Full jitter is sleep = random_between(0, min(cap, base × 2^n)). Equal jitter keeps half the backoff and randomizes the other half, so no retry is near-instant. Decorrelated jitter draws each sleep from the previous one, sleep = min(cap, random_between(base, sleep × 3)), so it does not depend on the attempt number at all. The calculator shows the sampled delay, the min–max range and the expected value for each so you can see the trade-off in your own numbers.
Back off, and read the header first. RFC 6585 section 4 defines 429 as "the user has sent too many requests in a given amount of time ('rate limiting')" and says the response "MAY include a Retry-After header indicating how long to wait before making a new request". When that header is present, wait that long, within a cap: the server knows its own quota window and your computed delay is a guess, but a bogus or enormous value must not stall the client for days, so the generated code clamps Retry-After at 10 minutes (lower it to your caller's deadline). When it is absent, fall back to exponential backoff with jitter. Two more rules from the same RFC: 429 responses "MUST NOT be stored by a cache", and the specification does not define how the server identifies a user or counts requests, so read the API's rate-limit documentation for the actual window.
Either an HTTP-date or a number of seconds. RFC 9110 section 10.2.3 defines Retry-After = HTTP-date / delay-seconds, where "a delay-seconds value is a non-negative decimal integer, representing time in seconds". The RFC's own examples are "Retry-After: Fri, 31 Dec 1999 23:59:59 GMT" and "Retry-After: 120". With a 503 (Service Unavailable) response it says how long the service is expected to be unavailable; with a 3xx redirect it is the minimum time to wait before following the redirect. The "Test a Retry-After header value" field in this tool parses either form, and the generated code does the same: an all-digit value is seconds; one of the three HTTP-date shapes in RFC 9110 section 5.6.7 (IMF-fixdate like the example above, or the obsolete RFC 850 and asctime forms) is a date; anything else, including "1.5", "-1", "1e3" or an ISO 8601 timestamp, is not guessed at and falls back to the computed backoff. The one exception is the curl loop, which honors only delay-seconds (an HTTP-date falls back to the computed backoff), because parsing a date portably in bash is not worth the lines.
Transient ones. Google Cloud's retry strategy documentation for Cloud Storage lists "HTTP 408, 429, and 5xx response codes" as responses that "indicate transient problems that are useful to retry", along with socket timeouts and TCP disconnects. Other 4xx responses (400, 401, 403, 404, 422) mean the request itself is wrong and will fail the same way again, so retrying them only adds load. The generated snippets retry 408, 429 and anything 500 and above, plus network errors such as DNS failures and connection resets, and return or raise everything else immediately.
All five snippets follow one contract. A response with any status outside 408, 429 and 5xx ends the loop at once: JavaScript, TypeScript, Python and Go hand that response back, whether it is a 200 or a 404, so the caller inspects the status; the bash script's only return value is its exit code, so it exits 0 for 2xx and 3xx and exits 2 for anything else that is not retried, which keeps a 401 or 404 from passing a health check. When the retries are used up on a retryable status, JavaScript and TypeScript return the last response (like fetch, an HTTP status never throws), Python raises requests.HTTPError with the last response on error.response, Go returns nil and an error naming the status, and bash exits 1. A network failure on the final attempt throws, raises or returns the error in the same way, and bash exits 1 for it too. With "Honor Retry-After" on, every snippet reads the header on every retried response, treats an all-digit value as seconds and one of the three RFC 9110 date shapes as a date (bash reads seconds only), clamps the result to 10 minutes, and falls back to the computed delay for anything else.
An idempotent request can be repeated without changing the outcome, so a retry after an ambiguous failure (a timeout where the first request may or may not have been processed) is safe. Google Cloud's retry documentation warns that "retrying requests that are not idempotent can lead to race conditions and other conflicts" and sorts its operations into three groups: always idempotent (all get and list requests, creating or deleting a bucket, deleting an HMAC key or notification), conditionally idempotent (writes that carry a precondition such as an etag or generation match, and deleting an object with a generation match, which "are only idempotent if they include specific optional arguments"), and never idempotent (for example creating a notification or a new key). Apply the same test to your own API: a POST that charges a card or sends an email needs an idempotency key before it is safe to wrap in a retry loop.
A retry budget is the total time or total number of extra requests a caller is allowed to spend on one logical operation. Max retries bounds the count; the "Total wait" and "Range" tiles in this tool bound the time, and the worst case is the number to compare with your caller's deadline. Google Cloud's own client libraries ship with such limits: the retry strategy page lists, for example, a maximum retry delay of 32 seconds and a total timeout of 50 seconds for the Java client, a 64-second maximum wait and a 600-second default deadline for Node.js, and a 60-second maximum wait with a 120-second default deadline for Python. Also keep retries at one layer: if a gateway, an SDK and your own loop each retry three times, one failure can turn into dozens of requests.
Full jitter draws uniformly between 0 and the capped exponential, so its mean is half of it. Equal jitter keeps half and draws the other half uniformly, so its mean is three quarters. Decorrelated jitter has no closed form because each delay depends on the previous draw and on the cap, so the tool averages 2,000 simulated runs and labels the result as a simulation mean. The "This sample" tile is one concrete draw from a seeded random generator, and "Re-roll jitter" draws the next one.
No. The schedule, the simulation and the code snippets are computed in your browser with JavaScript. Nothing you type is uploaded, and no account is needed.
Convert cURL commands to Python, JavaScript, Go, and PHP
Open →DeveloperSearch all HTTP status codes 1xx-5xx with descriptions and examples
Open →DeveloperBuild cron jobs visually — no syntax to memorize
Open →DeveloperFormat, validate & diff JSON — runs entirely in browser
Open →