Step 1 of 5 · Reading · ~4 min
Read
Why Rate Limit?
Why Rate Limit?
After this lesson your program is a tiny API gateway. It reads HTTP requests, counts how many each client has made in the current minute, and answers the sixth one with a real 429 Too Many Requests and a Retry-After header. Every later lesson swaps a smarter counter into this same gateway.
What a limiter protects you from
A limiter answers one question on every request, in microseconds: has this client already had its fair share? If yes, the request is refused before it costs anything - no database query, no worker thread, no invoice.
- The buggy client. A retry loop with no delay, 100 requests a second from one laptop, forever.
- The attacker. Credential stuffing on
/login, scraping on/search. If a request costs you more than it costs them, you lose by arithmetic. - The noisy neighbour. One tenant's batch job saturates the database and everyone else's latency spikes.
- The surprise bill. A runaway script on a pay-per-call backend.
Every limit is three decisions
| Decision | The question | Typical answers |
|---|---|---|
| Identity | Who is "the client"? | API key, user id, IP address, endpoint, everyone at once |
| Budget | How much, per how long? | 5 per minute, 10,000 per day |
| Verdict | What happens to the excess? | drop it (429), queue it, slow it down |
Here the identity is the X-API-Key header, the budget is a number per aligned window, and the verdict is to drop.
The wire contract
A request and the refusal it can earn, byte for byte (\r\n is the two bytes 13 and 10):
GET /v1/items HTTP/1.1\r\n
Host: api.example.com\r\n
X-API-Key: alice\r\n
\r\n
HTTP/1.1 429 Too Many Requests\r\n
Retry-After: 40\r\n
Content-Length: 0\r\n
\r\n
Three rules from the RFCs decide whether your gateway agrees with a real client.
- Status 429 is defined in RFC 6585. 403 would say "never", 503 would say "the server is sick"; 429 says "you, specifically, slow down".
Retry-After(RFC 9110, section 10.2.3) is a whole number of seconds, or a date. Never12.5. Round up, so a client that waits exactly that long is not refused again.- Header names are case-insensitive and the spaces around a value are not part of it (RFC 9110, sections 5.1 and 5.5).
x-api-key:aliceandX-API-KEY: aliceare the same client.
The simplest limiter: one counter per window
Chop the clock into aligned windows of W seconds: window k covers [k*W, k*W + W). Keep one counter per client for the window it last spoke in. A request is allowed if the counter is below the budget; allowing it increments the counter. A request in a new window starts from zero.
A worked trace with budget 3 and W = 10, one client, different numbers from your exercise:
| time | window | counter before | verdict | Retry-After |
|---|---|---|---|---|
| 100.0 | [100, 110) | 0 | allow | |
| 103.5 | [100, 110) | 1 | allow | |
| 109.0 | [100, 110) | 2 | allow | |
| 109.5 | [100, 110) | 3 | refuse | 1 (110 - 109.5 = 0.5, rounded up) |
| 110.0 | [110, 120) | 0 | allow |
Windows come from the clock, not from the client's first request: everyone's counter turns over at the same instant.
Two traps before you pick numbers
Retry storms. Clients that retry a 429 instantly turn a small overload into a flood, which is why you always send Retry-After, and why good clients back off with jitter.
Identity is slippery. Per-IP limits punish everyone behind one university or carrier NAT, and X-Forwarded-For is attacker-controlled unless your own proxy writes it. Prefer an authenticated identity when you have one.
Common mistakes
- Counting refused requests. A blocked client hammering the endpoint must not push its own counter, or the block could never end on its own.
- Anchoring the window to the first request. Then
Retry-Afterstops matching the boundary every other client sees. - A fractional or negative
Retry-After. Compute the gap to the window end and round it up. - Treating header spellings as different clients. Look the header up case-insensitively and trim it. A request with no key at all belongs to one shared
anonymousclient.
Your exercise
Implement the counter in the gateway below: 5 requests per client per 60-second window, 200 while the budget lasts, 429 with Retry-After after that. The starter already parses the bytes and writes the response head; you decide the verdict.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…