Rate Limiter
Enforces per-caller (per-user, per-IP, per-tenant) request budgets to protect downstream systems from abuse and overload.
Why it exists
Every service has a capacity ceiling. Without a rate limiter, one runaway client — buggy retry loop, aggressive scraper, DDoS — takes the whole service down. Rate limiting enforces fairness (no one caller consumes disproportionate capacity) and protection (the system stays healthy for the many even when one caller misbehaves). It's a required layer in any public API and many internal ones.
How it works
The rate limiter maintains a counter per caller key (API key, IP, user ID). On each request, it increments the counter and checks against the budget. If over, it returns 429 (Too Many Requests). Algorithms vary: fixed window (simplest, has burst issues at window boundaries), sliding window (smoother, more accurate), token bucket (allows bursts up to bucket size, refills at a rate), leaky bucket (smooths bursts to a constant output rate). Distributed rate limiters use Redis with INCR + EXPIRE.
Scaling characteristics
Local (per-instance) rate limiters are cheap but inaccurate at scale — 10 instances each allowing 100 RPS = 1000 RPS actual. Distributed rate limiters (Redis-backed) are accurate but add 1-2ms per request. Modern designs approximate globally with periodic sync: each instance has a local quota, syncing with a central store every N seconds — accurate to within a few percent, near-zero latency.
When to use it
- Any public API (per-key quotas by tier)
- Login endpoints (per-IP rate limits to prevent brute force)
- Expensive endpoints (search, ML inference)
- Anti-abuse (per-user upload limits, comment velocity)
- Multi-tenant SaaS (per-tenant fairness)
When NOT to use it
- Fully internal APIs where all callers are trusted and instrumented — a circuit breaker is often better
- Ultra-low-latency paths (sub-millisecond) — the rate limiter adds overhead you may not want
- Fully static content — the CDN handles this
Failure modes
- Redis outage → local fallback allows unlimited traffic (better than blocking everything). Design for this.
- Legitimate spike gets throttled (e.g., a product launch) — always feature-flag rate limits so you can raise them fast
- Global rate limiter becomes a hot key — shard by hash prefix
- Bucket bookkeeping drifts across instances — use approximate token bucket with periodic sync
- Rate limit response body is expensive to generate — use a static 429 payload
Alternatives
- API Gateway with built-in rate limiting (Kong, AWS API Gateway) — cheapest
- Envoy / service mesh with rate-limit service (envoy-rls) — for internal East-West traffic
- Circuit breaker — different tool, complementary; circuit breaker protects YOU from downstream failures, rate limiter protects downstream FROM you
- Load shedding at the load balancer — coarse but works when you're already overloaded
Interview questions
- Compare token bucket, leaky bucket, and sliding window. When would you use each?
- Design a distributed rate limiter for 1M API keys at 1M RPS. Where's the state?
- Your rate-limit Redis is down. What happens to traffic?
- You want to allow bursts up to 100 requests but sustain only 10/sec per user. Which algorithm?
- How would you implement per-endpoint rate limits (different budgets for different APIs) efficiently?
- How does rate limiting interact with retries? What can go wrong?
Systems that use this component
See how the real designs on this platform put rate-limiter to work — concrete usage context per system.
URL Shortener
Token bucket per IP for URL creation (100/hr anon, 1000/hr keyed)
Open systemTwitter/X timeline
Sliding window per user + per app + per endpoint
Open systemPayment System
Fraud-aware rate limits per card, per merchant, per IP
Open systemFollow/unfollow rate limits to prevent farms
Open systemIn 2013, Stripe published a blog post titled "Scaling your API with rate limiters." They had a problem: a single buggy client library was accidentally DDoSing their API — retrying failed requests without backoff, thousands of times per second. Rate limiting saved them. The post became the canonical reference on how to do this correctly, and the pattern spread.
Today, every serious API has rate limits. GitHub allows 5000 requests per hour per authenticated user. Twitter uses application-level + user-level tiers. Cloudflare rate-limits billions of requests a day at the edge. And every one of them implements the same 4 core algorithms: token bucket, leaky bucket, sliding window, and fixed window. Learning the trade-offs between these four is essential for any serious backend engineer.
Historical framing
- 1970s — Postel and Cerf discuss flow control in early TCP.
- 1986 — Turner formalizes leaky bucket for ATM networks (packet shaping).
- 1994 — Cisco IOS ships token bucket as class-based QoS.
- 2013 — Stripe publishes "Scaling your API with rate limiters" — the canonical web-API reference.
- 2017 — Cloudflare launches Rate Limiting product; industry adoption becomes standard.
- 2020s — Rate limiting becomes mandatory for API compliance (SOC 2, PCI).
The 4 core algorithms
Every rate limiter you'll ever see is one of these 4 algorithms, or a hybrid.
Interactive: token bucket in action
Watch the bucket fill up at rate R (tokens per second), and empty as requests arrive. Requests only pass if there's a token.
Fixed window vs sliding window — the boundary problem
Fixed window is the simplest algorithm — reset the counter every N seconds. But it has a nasty edge case: a burst at the window boundary can 2× the intended rate.
Distributed rate limiting — the hard part
Rate limiting on one server is easy. Rate limiting across 20 servers is hard — each needs to see the shared counter. The industry solution: Redis as a shared counter with atomic INCR.
The catch: Redis becomes a single point of failure. Cloudflare published a paper in 2019 on their solution: Redis Cell — a CRDT-based rate limiter that works even during Redis partition. For most non-planet-scale systems, plain Redis + INCR is sufficient.
Rate limit response — what to send back
When you reject a request, follow these HTTP conventions:
Where to enforce — layer decision
Product comparison
| Product | Algorithm | Scope | Cost | Best for |
|---|---|---|---|---|
| Cloudflare Rate Limiting | Sliding window (approx) | Edge, per-URL/IP | Free tier + $ | DDoS + API abuse at edge |
| AWS API Gateway | Token bucket | API + method + user | $3.50 / M req | AWS-hosted APIs |
| Kong Rate Limiting | Fixed window or sliding | Per-consumer, per-service | OSS free + enterprise | Self-hosted API gateway |
| Redis + INCR | Fixed window (basic) | Any (app-defined key) | OSS free | Custom app-level limits |
| Redis Cell | GCRA (leaky bucket variant) | Any (distributed-safe) | OSS free | Multi-region rate limiting |
| Nginx limit_req | Leaky bucket | Per-IP or per-key | OSS free | Edge reverse proxy |
| Envoy Rate Limit Service | Configurable | External gRPC service | OSS free | Service mesh (Istio, Linkerd) |
| Stripe idempotency + throttle | Token bucket + backoff | Per API key | Free for customers | Reference implementation |
Applied in real systems
Stripe — the canonical blog post
Stripe published 'Scaling your API with rate limiters' in 2013. They use per-API-key + per-endpoint token bucket, with Redis as the shared counter. Their limits: 100 read/s and 100 write/s by default per API key, higher on request.
GitHub — 5000 rq/h authenticated
Unauthenticated: 60 rq/h per IP. Authenticated: 5000 rq/h per token. GraphQL API has separate 5000 point-based limit. Returns X-RateLimit-Remaining + X-RateLimit-Reset headers on every response.
Twitter — application + user-context tiers
Two-tier: application-level rate limit (15 req/15 min for /tweets/search endpoint) + user-context limit. Uses sliding window per API endpoint. Free tier heavily throttled after 2023.
Cloudflare — edge-based DDoS + API protection
Runs at Cloudflare's 300+ edge PoPs before requests reach origin. Configurable via UI: match URL/method/header pattern, block/challenge/allow, response codes. Used by ~20% of the internet.
AWS API Gateway — throttle burst + rate
Two knobs: 'burst' (max concurrent requests) default 5000, 'rate' (steady-state requests/second) default 10000. Configurable per API + per stage + per method + per user via usage plans.
Nginx limit_req_module
Config: 'limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s'. Leaky bucket algorithm in shared memory. Simple, fast, no external state. Widely used at edge.
Redis Cell (Cloudflare 2019)
CRDT-based distributed rate limiter. Uses generic cell rate algorithm (GCRA). Works even during Redis partition. Available as Redis module. Cloudflare uses it globally.
Envoy Global Rate Limit Service
Envoy proxy delegates rate limit decisions to a gRPC service. Kubernetes-native. Used by Lyft (Envoy's origin) for east-west traffic + edge traffic protection.
Kong — pluggable rate limiting
Kong API Gateway ships with plugins for local (in-memory), Redis, or Cassandra-backed rate limits. Very popular in Kubernetes stacks.
Key takeaways
- 4 core algorithms: token bucket (bursts OK), leaky bucket (smooth), sliding window (accurate), fixed window (simple but boundary-vulnerable).
- Enforcement layer matters: CDN edge for DDoS, API gateway for auth-tier, app for feature-specific.
- Distributed = Redis. Atomic INCR with expiry. Redis Cell for CRDT-safety.
- Return proper headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After on 429.
- Fixed window has a boundary attack: if window resets at :00, a client can send 2× the limit by bursting at :59:59 and :00:00. Sliding window prevents this.
- Stripe's 2013 blog post is required reading.
References
- Stripe (2013) — Scaling your API with rate limiters. stripe.com/blog
- Cloudflare (2019) — Redis Cell + GCRA algorithm.
- Turner (1986) — Leaky bucket algorithm for ATM networks.
- RFC 6585 — HTTP 429 Too Many Requests status code.
- MDN docs — Retry-After header specification.