System Design Arena
Design a Rate Limiter
Protect APIs from abuse and overload: token buckets, sliding windows, distributed counters, and the fail-open/fail-closed decision.
Case Study
Rate Limiter
Same conversation, diagrams, and wrap-up you expectβnow framed with clearer scaffolding and iconography.
Prompt
Design a rate limiter that caps each client at N requests per time window across a fleet of API servers.
Interview snapshot
- β’ Topic: Rate Limiter
- β’ Expected depth: 45 - 60 minutes
- β’ Focus areas: APIs, scale estimation, resilient architecture
- β’ Wrap-up: risk, monitoring, disaster recovery
Key takeaways
- β’ Per-key limits (API key, user id, IP) with configurable window and burst.
- β’ Sub-millisecond decision latency at p99.
- β’ check(key, cost=1) β {allowed, remaining, retryAfter} β the only hot call.
ποΈ Interview mode
Practice this like a real interview
Don't read the answer first. Work the framework against the bare prompt, then compare against what the interviewer expected at each step.
Scorecard
What the interviewer is evaluating
Staff-level signal
Does the candidate pick an algorithm with a reason, keep the check atomic, and make the fail-open/fail-closed call explicitly tied to blast radius?
π§ Staff engineer judgment
The rate limiter must never become the outage. If your protection layer can take down the API it protects, you designed a single point of failure and called it safety.
Calibration
The common (bad) answer
if (count[key]++ > limit) reject count stored in each API serverβs memory
β Why this scores poorly: Per-server counters multiply the real limit by the server count, reset on deploys, and the increment races with itself. It limits nothing reliably.
β What a strong answer adds
- Token bucket: allows bursts, enforces the average β pick it and say why.
- Centralized atomic check (Redis + Lua): refill and decrement in one step.
- Shard by key; each key owned by one shard β counting stays exact.
- Explicit failure policy: fail-open with local fallback for rate limits.
- Hot-key rejection cache so abusers get cheap 429s.
Video walkthrough
Design a Distributed Rate Limiter
Hello Interview Β· ex-Meta staff engineer Β· 55 min
Token bucket vs sliding window, then the distributed-state problem that makes this question hard.
Work the prompt yourself first β guided practice grades your own answer step by step. Watching someone else design it feels like progress and teaches you far less.
Interview flow
Dialogue timeline
Interviewer
What exactly should it do?
Candidate
Enforce per-key limits β say 100 req/min per API key β across all API servers, return 429 with a Retry-After when exceeded, and support different limits per plan tier. Latency budget: sub-millisecond per check.
Interviewer
Which algorithm would you start with?
Candidate
Token bucket: capacity N, refill rate R. It allows short bursts up to capacity while enforcing the average rate β matching how real clients behave. Fixed windows have the boundary-burst flaw: 2N requests straddling a window edge.
Interviewer
Show me the bucket math.
Candidate
Store tokens and lastRefill per key. On request: tokens = min(capacity, tokens + (nowβlastRefill)Γrate); if tokens β₯ 1, decrement and allow, else reject. Two floats per key β the whole state is tiny.
Interviewer
Now make it work across 50 API servers.
Candidate
Centralize state in Redis: the check is a Lua script doing read-refill-decrement atomically β one round trip, no race between servers. Shard keys across a Redis cluster by hash; each key lives on exactly one shard, so counting stays consistent.
Interviewer
Redis adds a network hop. Isn't that slow?
Candidate
~0.5ms in-datacenter, inside budget. If it isn't, split the limit: each server enforces limit/serverCount locally with periodic sync β approximate but zero-hop. Precision versus latency is the actual trade-off to present.
Interviewer
Redis goes down. What now?
Candidate
Decide fail-open vs fail-closed out loud. For rate limiting, usually fail-open with a local fallback limiter β degraded protection beats a self-inflicted outage. For a billing quota, fail-closed. The failure policy IS the design decision.
Interviewer
A single hot key β one client at 1M req/sec.
Candidate
That key's shard becomes the bottleneck. Front it with a local pre-filter: once a key is known-exceeded, servers cache the rejection for the window remainder and stop asking Redis. Abusers get cheap 429s.
Interviewer
Where does this sit in the request path?
Candidate
As API-gateway middleware, before auth-heavy work β cheap rejection early. Config lives in a control plane: per-tier rules, hot-reloaded, so product can change limits without deploys.
Scoping
Requirements & trade-offs
Functional Requirements
- βPer-key limits (API key, user id, IP) with configurable window and burst.
- β429 + Retry-After on exceed; headers exposing remaining quota.
- βPer-plan tiers, hot-reloadable rules.
- βOut of scope: billing quotas, DDoS scrubbing.
Non-Functional Requirements
- βSub-millisecond decision latency at p99.
- βAccuracy: no boundary-burst leaks; bounded overshoot on failover.
- βThe limiter must never be the outage: explicit fail-open policy.
- βScales linearly with API fleet size.
Blueprint
Architecture modules
Module 1
API / Interface
- β’check(key, cost=1) β {allowed, remaining, retryAfter} β the only hot call.
- β’Response headers: X-RateLimit-Limit / -Remaining / -Reset.
- β’Control plane: PUT /rules {tier, capacity, refillRate}.
- β’Lua script keeps refill+decrement atomic in Redis.
Module 2
Back-of-the-Envelope
- β’1M req/sec API fleet β 1M limiter checks/sec; Redis cluster shard ~100K ops/sec each β ~10 shards.
- β’State per key ~50 bytes β 100M active keys β 5GB β memory is a non-issue.
- β’Local rejection cache turns hot-key floods into ~zero Redis load.
- β’0.5ms hop Γ 1 per request β batch or pipeline if gateway fans out.
Module 3
System Diagram Notes
- β’API Gateway middleware β Redis cluster (sharded token buckets, Lua CAS).
- β’Local fallback limiter + rejection cache inside each gateway.
- β’Control plane pushes rules to gateways (config bus).
- β’Metrics stream: per-key allow/deny rates feed abuse detection.
Module 4
Design Playbook
- β’Compare algorithms in one breath (fixed/sliding window, token bucket) and commit to one with a reason.
- β’The atomic Lua check is the correctness core β name the race it kills.
- β’Present precision-vs-latency as tunable, not as a flaw.
- β’The fail-open/fail-closed choice is the senior signal; tie it to blast radius.
- β’Mention the hot-key rejection cache β it separates you from textbook answers.