Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Error Handling, Timeouts, and Retries

โญโญ intermediateโณ 17 min read๐Ÿ“ Lesson 127 of 265

Map status codes to typed outcomes, time-box calls, and retry only what's safe to retry.

Typed outcomes, not exceptions for flow control

EnsureSuccessStatusCode() throws HttpRequestException for ANY failure โ€” you lose the status distinction. Map outcomes explicitly:

public sealed record ApiResult(bool Ok, string? Body, int Status);

public async Task<ApiResult> GetAsync(string path, CancellationToken ct)
{
    using var resp = await _http.GetAsync(path, ct);
    string body = resp.IsSuccessStatusCode
        ? await resp.Content.ReadAsStringAsync(ct) : null;
    return new ApiResult(resp.IsSuccessStatusCode, body, (int)resp.StatusCode);
}

Timeouts and cancellation

CancellationToken aborts the wait from the caller's side; HttpClient's Timeout (default 100 s) bounds the whole request. A timeout surfaces as TaskCanceledException/OperationCanceledException โ€” catch both deliberately.

Retries: only transient, only safe

Retry 5xx and network failures; NEVER retry 4xx (your request is wrong โ€” retrying hammers the server with the same mistake). Respect 429's Retry-After when present. Add exponential backoff with a cap, and make the token flow so cancellation still wins:

for (int attempt = 1; ; attempt++)
{
    try { return await SendOnceAsync(ct); }
    catch (HttpRequestException) when (attempt < maxAttempts) { }
    await Task.Delay(Math.Min(baseMs * (1 << (attempt - 1)), capMs), ct);
}

Retrying a non-idempotent POST (payments!) can double-charge โ€” safety is about the operation's semantics, not just the status code.

Check your understanding

  • Why not retry a 400? (The request is wrong; a retry repeats it.)
  • What does 1 << (attempt - 1) build? (Exponential backoff: 1x, 2x, 4xโ€ฆ)

โšก Now practice

Ready to Code
HTTP Practice: Handlers, Outcomes, RetriesDrive HttpClient through literal handlers, map statuses to typed results, and build a backoff retry loop.
4 challenges ยท ยท ~32 min