Rate limiting
Choose whose budget is counted, how it recovers, and which shared operation decides admission.
Predict first
Before you read: predict
ExamHall's result-download limiter uses a full token bucket: capacity 3, refill 1 token per second, request cost 1. For one key, 4 requests arrive at t=0, 4 at t=2, and 5 at t=20. Same-time requests are handled in listed order; rejected requests spend no credit.
How many requests are admitted in total?
The 30-second version
A limiter assigns each key a budget. Token buckets replenish credit up to capacity; admission consumes request cost, while insufficient credit rejects it. S1 Fixed windows reset by period; rolling logs retain recent admissions. Weighted counters approximate history. S3 Atomic accounting enforces one shared budget. S4 Quota responses communicate rejection and retry information. S2
Choose whose budget is counted, how it recovers, and which shared operation decides admission.
How it works
Step 1 of 8
Analogy: An arcade play-credit dispenser
Imagine an arcade with a separate capped credit tray for each player. A dispenser adds credit steadily, and an attendant spends the required credit before starting a game. Unspent credit can support a burst of plays, but a long absence cannot fill the tray beyond its rim. This is an authored structural analogy, not a description of commercial arcade hardware.
| Arcade relationship | Limiter relationship |
|---|---|
| One labelled tray per player | One accounting state per budget key |
| A dispenser adds credit until the rim | Refill stops at bucket capacity |
| A game consumes its stated price | Admission debits request cost |
| A new dated coupon sheet each hour | A fixed window creates a new allowance |
| Dated used coupons remain for inspection | A rolling log retains individual timestamps |
| Attendants consult one tray | Workers share one budget |
Breaks down when: paper coupons cannot provide Redis atomicity; physical dispensers need not refill continuously; people can transfer credits, but keys do not merge by themselves. Contrasting case: checking how many arcade seats are occupied measures current occupancy, not a player's spending rate. In real terms: a rate limit applies a chosen temporal allowance to a chosen identity; it does not prove that the service has spare capacity. S2
Examples, run for real
4.1 ExamHall: summary polling consumes export credit
EXH-1930 is a synthetic results-pipeline replay, not a production incident report. One tenant sends two summary reads followed by three export reads. Both routes have allowance two in this exercise. The first implementation keys by tenant; MR !2130 adds the route budget identifier. S2
ExamHall tenant accepted=2 rejected=3
ExamHall tenant+route accepted=4 rejected=1
The shared counter silently couples two workflows; adding the intended route scope separates their allowances (step 1). It does not increase either individual allowance. A policy that deliberately limits a tenant across all routes should retain the broader key. The observed difference demonstrates that the key is part of the product contract, not just a storage naming choice. S2
4.2 Payments ledger: expiring the left boundary
A ledger client sends three reads at virtual t=0 and three at t=10. With allowance three and a ten-second interval, both implementations admit the second batch: the fixed period has changed, and the rolling log removes timestamps exactly on its left boundary. That agreement is specific to this spacing. The code uses actual Redis sorted-set removal, not a sleep. S3
payments fixed=6 rolling=6
4.3 The local HTTP boundary
Three requests reach a loopback ledger route at one virtual instant. Its token policy has two initial credits. The real HTTP server forwards admissions and returns a quota response for the denied request. The script checks the response header; its suggested delay is calculated from the configured refill and never measured with a stopwatch. S2 S5
HTTP statuses=200,200,429 Retry-After-present=true
This run proves that the authored route emits its contract. Whether an HTTP field is required comes from the RFC, not from a server that was programmed to emit it. The fixture sends no production traffic and makes no claim about a client's eventual retry success. S2
Picture it

Try it
Answer first, then check. Nothing is saved.
Question 1 of 5
A ledger client sends four requests at virtual t=9 and four at t=10. Both policies start empty, allow four per ten seconds and count admissions: one uses fixed periods starting at zero; the other uses an exact rolling log.
What are their total admissions, in that order?
Question 2 of 5
A catalog's shipped limiter allows one admission per source IP in the current period. Product policy grants signed-in users independent allowances inside each organization; summary reads have a separate allowance from exports. Starting empty, replay these six requests before any reset: 1 north/alex/export/ip-x; 2 south/alex/export/ip-y; 3 north/alex/summary/ip-x; 4 north/bea/export/ip-x; 5 north/alex/export/ip-x; 6 south/alex/summary/ip-y.
Which requests does the shipped limiter reject that the product policy would admit?
Question 3 of 5
A payments-ledger endpoint replies with status 429 and a JSON explanation of its quota, but no Retry-After field.
Which review conclusion follows from RFC 6585?
Question 4 of 5
Two ten-second ledger traces each have four previous-period admissions: at t=1 in the early trace, or t=9 in the late trace. At t=12, five requests arrive for each key; the allowance is six.
Which classification result does the comparison produce?
Question 5 of 5
At virtual t=11, two workers share Redis key north:1 for the current ten-second period. It holds one; the allowance is two accepted requests. The shipped client code is used = GET(key); if used < 2: INCR(key); admit. For two requests, both GETs finish before either INCR. Each candidate is one atomic Lua body, shown as pseudocode; key stays north:1.
Which replacement keeps accepted-request accounting consistent when replaying from that state?
Go deeper
Pro tip
Replay a saved request sequence against both policies when changing a limiter, and compare per-route rejections. Aggregate traffic can conceal an allowance shared by unrelated workflows. Counts, not response durations, explain the policy difference. S2
Sources
- S1: RFC 3290 §5.1.3 and Appendix A, informational Diffserv model. Fetched 2026-09-30. Supports capped credit and strict whole-cost conformance; this lab adapts packet cost to request cost. This implementation reference extends the curriculum source list under codex-w5a CG-003; no cited assessment key depends on it.
- S2: RFC 6585 §4, fetched 2026-09-30. The curriculum's canonical 429 reference; identity scope, optional waiting hint, and cache restriction.
- S3: Cloudflare's named original account, How we built rate limiting capable of scaling to millions of domains, fetched 2026-09-30. Fixed-period boundaries, timestamp accounting and weighted approximation. This lesson does not claim its Redis code reproduces Cloudflare's distributed implementation.
- S4: Redis scripting introduction, fetched 2026-09-30. Atomic Lua execution; supporting implementation source logged under codex-w5a CG-002. General Redis data structures remain the sibling's lane.
- S5: RFC 9110 §10.2.3, fetched 2026-09-30. Retry-After grammar and interpretation. No draft RateLimit header syntax is taught or graded.
- These support fixture observations only. All fetched source snapshots have URL/date/SHA256 headers under
sources/.
Related lessons
Checked against the tools and docs as of 2026-09-30.