Rate Limiter
Level: Advanced 60–90 minConcepts: AlgorithmsMockingBoundariesStateDesign Patterns
Solutions: C# | TypeScript | Python
Implement a rate limiter that restricts the number of requests a client can make within a time window.
Requirements
- Create a
RateLimiterwith a configurable max requests and time window (in seconds) allowRequest(clientId)— returnstrueif the request is allowed,falseif rate limited- Each client is tracked independently
- Use a sliding window — requests older than the window are no longer counted
- The limiter should accept an injectable clock/time source for testability
Test Cases
Given a rate limiter configured for 3 requests per 10 seconds:
| Time (s) | Client | Action | Result |
|---|---|---|---|
| 0 | ”alice” | allowRequest | true (1/3) |
| 1 | ”alice” | allowRequest | true (2/3) |
| 2 | ”alice” | allowRequest | true (3/3) |
| 3 | ”alice” | allowRequest | false (limit reached) |
| 3 | ”bob” | allowRequest | true (bob is separate, 1/3) |
| 11 | ”alice” | allowRequest | true (first request expired) |
| 11 | ”alice” | allowRequest | true (second request expired) |
| 11 | ”alice” | allowRequest | false (third request still in window) |
Bonus
- Implement token bucket algorithm as an alternative strategy
- Add
remainingRequests(clientId)— returns how many requests the client has left - Add
retryAfter(clientId)— returns seconds until the next request will be allowed - Add burst allowance — permit short bursts above the rate limit with a separate burst quota
- Support tiered limits — different rate limits for different client tiers (free, pro, enterprise)
Reference Walkthrough
Full C#, TypeScript, and Python implementations live at tddbuddy-reference-katas/rate-limiter. Nineteen scenarios across rule construction, allow-under-limit, reject-at-limit with retryAfter, window-expiry reset, per-key isolation, and an end-to-end worked example — shared across all three languages — with a Limiter aggregate, Rule value type, Decision union (Allowed | Rejected(retryAfter)), Clock collaborator, LimiterBuilder + FixedClock test doubles, and LimiterRuleInvalidException for invalid rule config.
- C# (.NET 8, xUnit, FluentAssertions) — walkthrough
- TypeScript (Node 20, Vitest, strict types) — walkthrough
- Python (3.11, pytest, frozen dataclasses, Protocol clock) — walkthrough
This kata ships in Agent Full-Bake (F3) mode: one commit per language with the full domain design landing together. The walkthroughs read as design rationale — why Decision is a union, why Rule is a value type, why Clock is injected (same pattern as memory-cache and circuit-breaker).
The reference uses the fixed-window algorithm rather than the sliding window described in the brief above. Fixed window collapses to a two-variable state machine (windowStart, count) per key and is the simplest algorithm to teach — every behavior in the scenario list (allow, reject, reset, retryAfter, isolation) is exercisable against it, and the end-to-end worked example in WorkedExampleTests walks the full cycle for alice and bob at 3 requests per 10 seconds. The brief’s sliding-window variant is a natural next step: swap WindowState for a Deque<DateTime> of request timestamps and prune on entry. The bonus items (token bucket, remainingRequests, burst allowance, tiered limits) are deliberately out of scope — the Rule and Decision types are ready to extend without the reference hiding seams under premature abstraction. See the repo’s Gears section for when middle gear is the right call.