TypeDrop
2026-06-25 Challenge
2026-06-25
Medium
Typed API Rate Limiter with Sliding Window & Policy Registry
You're building the rate-limiting middleware for a multi-tenant REST API gateway. Raw policy configurations arrive as `unknown` from a remote config store; your limiter must validate them, look up the correct typed policy per tenant, track request counts in a sliding-window algorithm, and return a strongly-typed admission decision — with zero `any`.
Goals
- Implement `registerPolicy` to safely narrow `unknown` → `RateLimitPolicy` and return a typed `Result<T,E>` with specific `PolicyValidationError` variants.
- Implement `admit` to dispatch on the policy's discriminated `kind` and apply the correct fixed-window or sliding-window admission algorithm.
- Implement `snapshot` to return a `Record<TenantId, …>` reflecting each tenant's in-window request count and reset time at the given `now`.
- Use branded types throughout — never conflate raw `string`/`number` with `TenantId`, `RequestId`, or `EpochMs`.
challenge.ts
// Core types and main class signature
export type TenantId = string & { readonly __brand: "TenantId" };
export type EpochMs = number & { readonly __brand: "EpochMs" };
export type RateLimitPolicy =
| { readonly kind: "fixed"; readonly maxRequests: number; readonly windowMs: number }
| { readonly kind: "sliding"; readonly maxRequests: number; readonly windowMs: number }
| { readonly kind: "tiered"; readonly tier: string;
readonly tiers: Record<string, { maxRequests: number; windowMs: number }> };
export type Result<T, E> =
| { readonly ok: true; readonly value: T }
| { readonly ok: false; readonly error: E };
export type AdmissionDecision =
| { readonly decision: "allowed"; remainingRequests: number; windowResetAt: EpochMs }
| { readonly decision: "denied"; retryAfterMs: number; windowResetAt: EpochMs };
export class RateLimiter {
registerPolicy(tenantId: TenantId, raw: unknown): Result<RateLimitPolicy, PolicyValidationError>;
admit(tenantId: TenantId, requestId: RequestId, now: EpochMs): AdmissionDecision;
snapshot(now: EpochMs): Record<TenantId, { policy: RateLimitPolicy; requestsInWindow: number; windowResetAt: EpochMs }>;
}
Hints (click to reveal)
Hints
- To narrow `unknown` safely, start with `typeof raw === 'object' && raw !== null` before accessing any fields — then use `'kind' in raw` and check the value.
- For the sliding window, store timestamps as a mutable copy of the `readonly` array: `[...existing, now]` creates a new array you can assign back without fighting `readonly`.
- The `Record<TenantId, …>` return type of `snapshot` requires `TenantId` as the index — use `Object.fromEntries` or build up a plain object cast through the branded key type.
Useful resources
Or clone locally
git clone -b challenge/2026-06-25 https://github.com/niltonheck/typedrop.git