Notes
Why
Why limit? To protect capacity from one noisy or buggy client. To stop abuse, like password guessing and scraping. To control cost when every call costs you money. And for fairness, like a free plan getting sixty requests a minute and a paid plan six hundred.
- protect capacity from one
- noisy or buggy client
- stop abuse: password guessing,
- scraping, spam sign-ups
- control cost (paid APIs, LLMs)
- fairness: plans and quotas
- (free: 60/min, pro: 600/min)
Where
Put the limiter at the edge, in the API gateway or Nginx, so rejected requests cost almost nothing. Count per key: user id, API key, IP address, or user plus endpoint. Over the limit? Return 429 Too Many Requests with a Retry-After header saying when to come back.
- at the edge: API gateway,
- load balancer, Nginx
- (reject before doing work)
- key: user id, API key, IP,
- or user + endpoint
- response: 429 Too Many Requests
- Retry-After: 30
Fixed window
Algorithm one: fixed window. One counter per user per minute. Each request increments it; over a hundred, reject. Very simple, one number per user. The flaw: a client can send a hundred at twelve oh five fifty-nine and another hundred at twelve oh six, two hundred requests in two seconds.
limit 100 per minute key = user:42:12:05 INCR → if > 100, reject simple, one counter flaw: 100 at 12:05:59 + 100 at 12:06:00 = 200 in two seconds
Sliding log
Two: sliding window log. Store the timestamp of every request. On each new request, drop the ones older than a minute and count the rest. It's exact, with no edge burst. But it stores one entry per request, which is a lot of memory for a busy API.
keep a timestamp per request (sorted set) on each request: drop entries older than 60 s count < 100 ? allow : reject exact, no edge burst memory: one entry per request
Sliding counter
Three: sliding window counter, the practical middle. Keep this minute's and last minute's counts. Fifteen seconds into the minute, estimate the last sixty seconds as this minute's count plus three quarters of last minute's. Thirty plus sixty is ninety: allowed. Nearly exact, and only two numbers per user.
two fixed counters:
previous minute: 80
current minute: 30
15 s into the current minute
estimate = 30 + 80 × 45/60
= 90 → allow
close to exact, 2 numbers Token bucket
Four: token bucket, the most common in real systems. Each user has a bucket of up to ten tokens that refills at one per second. Each request takes a token; empty bucket, rejected. It allows short bursts up to the bucket size while enforcing a steady average. You only store the token count and the last refill time.
bucket holds up to 10 tokens refills 1 token per second request takes 1 token empty bucket → reject allows short bursts (10) with a steady average (1/s) store: tokens + last refill time
Leaky bucket
A cousin, the leaky bucket, puts requests in a queue that drains at a fixed rate. The output is perfectly smooth; bursts wait or are dropped. It's ideal in front of something with a hard capacity, like a payment provider or an SMS gateway that allows exactly fifty calls a second.
- requests enter a queue that
- drains at a fixed rate
- output is perfectly smooth
- bursts wait or are dropped
- good for protecting a
- downstream with a hard
- capacity (payment provider,
- SMS gateway)
Distributed
You'll have many gateway servers, but one limit per user, so counters live in a shared store like Redis. Increment and expire in one atomic step, usually a small Lua script, to avoid races. Decide what happens if Redis is down: most APIs fail open and allow traffic, but login and payments fail closed.
10 gateways, one limit per user → counters in Redis INCR user:42:min EXPIRE 60 (Lua script = atomic check-and-update) Redis down? fail open (allow) for most APIs, fail closed for login
Decision
Which one? Public API quotas: token bucket or sliding window counter. Login and one-time-password abuse: a sliding window keyed by both IP and account. Protecting a downstream with a fixed capacity: leaky bucket. And when you just need something now, a fixed window is fine.
- public API quotas:
- token bucket or
- sliding window counter
- login / OTP abuse:
- sliding window, per IP + user
- protect a fixed-capacity
- downstream: leaky bucket
- quick and good enough:
- fixed window
Say it like this
'Design a rate limiter' is a classic question. Cover where it runs, what it's keyed on, the algorithm and why, token bucket for burst plus average, where the state lives, Redis with atomic scripts, what the client gets back, 429 with Retry-After, and what happens when the limiter's store fails.
- "Design a rate limiter"
- where: gateway at the edge
- key: user / API key / IP
- algorithm: token bucket
- (burst + average)
- state: Redis, atomic Lua
- response: 429 + Retry-After
- failure: fail open vs closed