TypeDrop

2026-09-04 Challenge

2026-09-04 Medium

Typed HTTP Retry Client with Exponential Backoff & Error Classification

You're building the resilient HTTP layer for a microservice platform. Transient network errors and rate-limit responses should be retried with exponential backoff, while client errors (4xx) must be surfaced immediately — all with fully typed request/response shapes and a structured Result type so callers never need to guess what went wrong.

Goals

  • Define the `HttpError` discriminated union and the generic `Result<T, E>` / `HttpResult<T>` types.
  • Implement `classifyResponse` to map raw HTTP status codes to typed `HttpResult<string>` values.
  • Implement `fetchWithRetry` with correct exponential backoff, retry-kind filtering, and attempt capping.
  • Implement `renderError` with an exhaustive switch that the compiler verifies via the `never` pattern.
challenge.ts
// Key types and main function signature

type HttpStatus = number & { readonly __brand: "HttpStatus" };

type HttpError =
  | { kind: "network"; message: string }
  | { kind: "client";  status: HttpStatus; body: string }
  | { kind: "server";  status: HttpStatus; body: string };

type Result<T, E> =
  | { ok: true;  value: T }
  | { ok: false; error: E };

type HttpResult<T> = Result<T, HttpError>;

interface RetryPolicy {
  maxAttempts: number;
  baseDelayMs: number;
  retryOn: ReadonlyArray<"network" | "server">;
}

type Transport = <TBody>(config: RequestConfig<TBody>) => Promise<RawResponse>;

async function fetchWithRetry<TBody>(
  config: RequestConfig<TBody>,
  transport: Transport,
  policy: RetryPolicy,
): Promise<HttpResult<string>> { /* TODO */ }
Hints (click to reveal)

Hints

  • A discriminated union's `kind` field lets TypeScript narrow automatically — no type assertions needed inside `classifyResponse` or `renderError`.
  • In `fetchWithRetry`, track `lastError: HttpError` across loop iterations so you can return it once `maxAttempts` is exhausted.
  • For the exhaustive check in `renderError`, add a `default:` branch that assigns `error` to a variable typed as `never` — TypeScript will error if any `kind` is unhandled.

Or clone locally

git clone -b challenge/2026-09-04 https://github.com/niltonheck/typedrop.git