Skip to content
Lesson 1 of 8

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

DecisionThe questionTypical answers
IdentityWho is "the client"?API key, user id, IP address, endpoint, everyone at once
BudgetHow much, per how long?5 per minute, 10,000 per day
VerdictWhat 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. Never 12.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:alice and X-API-KEY: alice are 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:

timewindowcounter beforeverdictRetry-After
100.0[100, 110)0allow
103.5[100, 110)1allow
109.0[100, 110)2allow
109.5[100, 110)3refuse1 (110 - 109.5 = 0.5, rounded up)
110.0[110, 120)0allow

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-After stops 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 anonymous client.

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.

Up nextFixed Window CounterAlgorithms

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…