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.
Useful resources
Or clone locally
git clone -b challenge/2026-09-04 https://github.com/niltonheck/typedrop.git