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.

Functional RequirementsNon-Functional RequirementsAPI / Interface

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.

🎯 Start guided practice (free)

Scorecard

What the interviewer is evaluating

Requirements8/10
Scale estimation6/10
API design6/10
Architecture8/10
Trade-offs9/10
Failure handling9/10

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.