CONCEPT SHEET · Concepts

Rate Limiting

One-liner: Cap how many requests a client can make per window to protect capacity and enforce fairness; token bucket is the default answer.

ArticleConcepts© Arthur Bernier Jr583 words2 code blocks

Say this#

How it works#

Token bucket in Redis (atomic Lua):

TEXT
tokens = min(capacity, tokens + (now - last) * rate)if tokens >= 1: tokens -= 1; allow  else: deny, Retry-After = (1 - tokens)/ratestore tokens,last with TTL

Sliding window counter: count = prev_window_count * (1 - elapsed_fraction) + curr_window_count; allow if ≤ limit.

Placement:

TEXT
client -> CDN/WAF (per-IP, DDoS) -> API gateway (per key, per route; Redis) -> service (per tenant, per resource)
  1. Enforce at the gateway so services don't duplicate it; use Redis with a Lua script so check-and-decrement is atomic.
  2. Return 429 with Retry-After; clients back off exponentially with jitter.
  3. Make limits visible via headers; document them.
  4. Fail open or closed when Redis is down? Usually fail open with a local fallback limiter.

Trade-offs#

ChoiceProCon
Token bucketAllows bursts, simple, O(1) memoryBurst can still hurt if b is large
Leaky bucketPerfectly smooth egressAdds latency; queue can overflow
Fixed windowTrivial, cheap2x burst at boundaries
Sliding logExactO(requests) memory per key
Sliding counterNearly exact, O(1)Approximation assumes uniform distribution
Centralized RedisGlobal accuracyExtra hop per request; Redis is a dependency
Per-node localNo networkLimit × node count drift; uneven LB makes it worse

Numbers#

  • Redis INCR-based limiter: ~0.2-1 ms added per request; single Redis node sustains ~100k ops/s order of magnitude.
  • Fixed window worst-case burst: 2x the limit in a span shorter than the window.
  • Typical public API limits: GitHub 5,000 req/h per authenticated user; Stripe ~100 req/s in live mode; Twitter/X per-15-minute windows.
  • HTTP status: 429; include Retry-After in seconds.

In the interview#

See also