Skip to main content
rate-limiter

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?
DEEP DIVE — INTERACTIVE
Interactive walkthrough with animated visuals

In 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.

Token Bucket
How it works: Bucket holds up to B tokens. Refills at rate R per second. Each request consumes 1 token. No tokens = reject.
Allows bursts?
✓ Yes
Accuracy
Good
Complexity
Simple
Used in
AWS API Gateway, Stripe, GitHub

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.

Token bucket — step 1 of 6
Bucket cap=55 tokens→ requests
Starting state — bucket holds 5 tokens (capacity=5). Refill rate: 2 tokens/sec.

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.

Fixed window: the boundary attack
Fixed window · vulnerable
Limit: 10 req / minute
Window 12:00:00-12:01:00
12:00:59 — sends 10 requests ✓
12:01:00 — sends 10 more ✓
Result: 20 requests in 1 second — 2× the limit!
Sliding window · protected
Limit: 10 req / rolling minute
Checks last 60 seconds
12:00:59 — sends 10 requests ✓
12:01:00 — REJECTED (last 60s = 10)
Result: exactly 10 in any 60-second window. Bulletproof.

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.

Distributed rate limiting — step 1 of 6
LBServer 1Server 2Server 3Rediscounter12 / 100
3 API servers behind an LB. Redis counter for user 'alice' at 12 requests.

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:

X-RateLimit-Limit
100
Every response
Client knows the per-window cap.
X-RateLimit-Remaining
42
Every response
Client tracks remaining budget.
X-RateLimit-Reset
1700503200
Every response
Unix timestamp when the window resets.
Retry-After
60
429 response
Seconds until client should retry. Standardized by RFC 6585.
HTTP status 429
Too Many Requests
Rate limit hit
Signals throttling. Different from 503 (server error).

Where to enforce — layer decision

CDN edge
✓ Pros: Absorbs DDoS before it reaches you. Free at scale.
✗ Cons: Coarse-grained (IP-based). Not aware of your users.
Cloudflare, Fastly, AWS Shield
API gateway
✓ Pros: Auth-aware (per API key + user). Centralized policy.
✗ Cons: Single choke point. If gateway is slow, everything is.
AWS API Gateway, Kong, Envoy
Application
✓ Pros: Full context — per user, per feature, per resource.
✗ Cons: Distributed across N app servers. Needs shared state (Redis).
Redis + INCR, Rack::Attack, express-rate-limit

Product comparison

ProductAlgorithmScopeCostBest for
Cloudflare Rate LimitingSliding window (approx)Edge, per-URL/IPFree tier + $DDoS + API abuse at edge
AWS API GatewayToken bucketAPI + method + user$3.50 / M reqAWS-hosted APIs
Kong Rate LimitingFixed window or slidingPer-consumer, per-serviceOSS free + enterpriseSelf-hosted API gateway
Redis + INCRFixed window (basic)Any (app-defined key)OSS freeCustom app-level limits
Redis CellGCRA (leaky bucket variant)Any (distributed-safe)OSS freeMulti-region rate limiting
Nginx limit_reqLeaky bucketPer-IP or per-keyOSS freeEdge reverse proxy
Envoy Rate Limit ServiceConfigurableExternal gRPC serviceOSS freeService mesh (Istio, Linkerd)
Stripe idempotency + throttleToken bucket + backoffPer API keyFree for customersReference implementation

Applied in real systems

Stripe

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 API

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/X API

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 Rate Limiting

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

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

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

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 Rate Limit Service

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 Rate Limiting Plugin

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.