Retry & backoff
Resilient retry patterns with full jitter examples
Use retries sparingly for transient 429 / 500 / 502 / 503 responses only. Permanent errors (400 / 401 / 403 / 404) must not be retried.
Retry & backoff snippets
Use these as starting points; tailor them to your use case.
TypeScript example
async function retry<T>(action: () => Promise<T>, opts: {
retries?: number;
baseMs?: number; // initial delay
factor?: number; // exponential factor
maxMs?: number; // cap
shouldRetry?: (e: any) => boolean;
} = {}): Promise<T> {
const {
retries = 5,
baseMs = 400,
factor = 2,
maxMs = 15_000,
shouldRetry = (e) => {
if (!e || !e.response) return false;
const s = e.response.status;
return s === 429 || (s >= 500 && s < 600);
},
} = opts;
let attempt = 0;
while (true) {
try {
return await action();
} catch (e: any) {
if (attempt >= retries || !shouldRetry(e)) throw e;
const expo = Math.min(baseMs * Math.pow(factor, attempt), maxMs);
const jitter = Math.random() * expo; // Full jitter
await new Promise(r => setTimeout(r, jitter));
attempt++;
}
}
}
// Usage
const client = async () => {
return fetch('https://data-api.tibber.com/v1/homes', {
headers: { 'Authorization': `Bearer ${token}`, 'User-Agent': 'MyIntegration/1.0.0 tibber-data-js/0.0.1' }
}).then(r => {
if (!r.ok) {
const err: any = new Error(`HTTP ${r.status}`);
err.response = r;
throw err;
}
return r.json();
});
};
retry(client).then(console.log).catch(console.error);C# example (.NET 8 minimal)
(Without external dependencies; adapt to Polly if you prefer.)
static async Task<T> RetryAsync<T>(Func<Task<T>> action, int retries = 5, int baseMs = 400, int maxMs = 15_000)
{
for (var attempt = 0; ; attempt++)
{
try { return await action(); }
catch (HttpRequestException ex) when (ShouldRetry(ex, attempt, retries))
{
var delay = CalcDelay(baseMs, attempt, maxMs);
await Task.Delay(delay);
}
}
static bool ShouldRetry(HttpRequestException ex, int attempt, int retries)
{
if (attempt >= retries) return false;
if (ex.StatusCode is HttpStatusCode.TooManyRequests or >= HttpStatusCode.InternalServerError) return true;
return false;
}
static TimeSpan CalcDelay(int baseMs, int attempt, int maxMs)
{
var expo = Math.Min(baseMs * Math.Pow(2, attempt), maxMs);
var jitter = Random.Shared.NextDouble() * expo; // full jitter
return TimeSpan.FromMilliseconds(jitter);
}
}
// Usage with HttpClient
using var http = new HttpClient();
http.DefaultRequestHeaders.UserAgent.ParseAdd("MyIntegration/1.0.0 tibber-data-dotnet/0.1.0");
http.DefaultRequestHeaders.Authorization = new("Bearer", token);
var homes = await RetryAsync(async () =>
{
using var resp = await http.GetAsync("https://data-api.tibber.com/v1/homes");
if (!resp.IsSuccessStatusCode)
throw new HttpRequestException($"HTTP {(int)resp.StatusCode}", null, resp.StatusCode);
return await resp.Content.ReadAsStringAsync();
});Anti-patterns
- Retrying forever
- Retrying
400/401/403/404 - Identical fixed delays (thundering herd risk)
- Parallel floods of retries after a single incident
Next: see Troubleshooting or return to Rate limiting.
