API Rate Limiting, Explained
Why every public API caps how often a client can call it, and the common algorithms behind that cap.
2 min read
Rate limiting caps how many requests a client can make in a given time window. Without it, a single misbehaving client — a buggy retry loop, a scraper, or a deliberate attacker — can exhaust a backend's resources or run up costs on downstream services, degrading the experience for every other user at once.
Why every serious API has one
- Abuse prevention — stops brute-force login attempts, scraping, and denial-of-service style traffic.
- Fairness — one client's traffic spike shouldn't starve everyone else's requests.
- Cost control — protects expensive resources (a database, a paid third-party API you're proxying) from runaway usage.
- Stability — keeps the backend inside the load it was actually provisioned to handle.
Common algorithms
Fixed window — count requests per client in a fixed time bucket (e.g. per minute), reset the counter when the window rolls over. Simple, but bursty traffic right at a window boundary can let through nearly double the intended limit.
Sliding window — instead of a hard reset, the window continuously slides, smoothing out the boundary-burst problem of fixed windows at the cost of slightly more bookkeeping.
Token bucket — each client has a bucket that holds up to N tokens, refilled at a steady rate; each request consumes a token, and requests are rejected when the bucket is empty. This naturally allows short bursts (using up saved tokens) while still enforcing a steady average rate — the most common choice in production systems.
What it looks like from the client's side
GET /api/search?q=rust HTTP/1.1
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1732650000A well-behaved API returns 429 Too Many Requests with a Retry-After header telling the client exactly how long to wait, plus X-RateLimit-* headers so clients can pace themselves proactively instead of hitting the limit blind.
Where limits are usually enforced
Rate limiting is almost always implemented as middleware (see the previous lesson), running early in the request pipeline so a rejected request never reaches expensive application or database logic. The counters themselves typically live in a fast, shared store like Redis rather than in each server's local memory — local counters would let a client simply spread requests across multiple server instances to bypass the limit entirely.
Choosing the key and the tiers
Limits are usually keyed by API key or user ID for authenticated traffic, and by IP address for anonymous traffic — IP-based limiting alone is weaker since many real users can share one IP (behind a corporate NAT, for instance) or an attacker can rotate IPs. Most APIs also tier limits: stricter for anonymous or free-tier traffic, looser for authenticated or paid traffic, and often near-unlimited for trusted internal services.
Rate limiting protects a backend from too many requests. The next lesson covers something just as easy to get wrong for the opposite reason — leaking the secrets that protect a backend in the first place.