Why this matters: the rate limiter is the machine-coding problem where the requirements conversation contains an ambush. Somewhere in it, you'll ask what a limit like "100 per minute" actually means under bursty traffic — and a good interviewer will turn the question around: you pick an algorithm and defend its burst behavior. Candidates who haven't compared the algorithms before the round get chosen for; candidates who have, choose.
The prompt, as given
Design a rate-limiter library.
Other services will embed your library and call it on every request: given some key — a user id, an API token, an IP — it answers one question: is this request allowed right now, or not?
This is a library, not a service: it runs inside the caller's process, and its API is the product.
The last line is the frame to hold onto. Nothing here is distributed. The product is an API called on someone's hot path, millions of times an hour, and every requirement question should be read through that lens.
The questions, and why each one matters
"What does a limit look like?" "N requests per window" — 100 per minute, say. Simple to state; the follow-up is where the design lives.
"Does 100/minute mean 100 in the first second is fine?" This is the burst question, and expect it turned around on you: the answer is a property of the algorithm you pick, so the interviewer wants your comparison, not their ruling. Hold the thought — lesson 02 does the enumeration properly. What you're pinning here is that burst behavior is yours to define and defend.
"How many distinct keys?" Potentially millions — per-user, per-token. This is the question that makes memory a requirement: whatever state you keep per key, multiplied by millions, must stay bounded. A limiter that grows without limit is its own denial of service.
"What's the allow() contract?" Synchronous, non-blocking, on the caller's hot path — microseconds matter. It returns a decision; it never sleeps, never waits for capacity. This contract quietly outlaws whole categories of design (anything that schedules, queues, or blocks) and you want it on the record early.
"What do we tell a denied caller?" Just "no," or "no, retry in 340ms"? A retry hint is a real API decision with a real cost — whether it's cheap depends entirely on the algorithm's state, which is another reason the algorithm choice is yours to own.
"What happens if the limiter itself is broken?" State corrupted, store unavailable — does the library fail open (allow everything) or fail closed (deny everything)? There is no universal answer: think a login endpoint versus a public read-only API. What's graded is having a posture and defending it per use case; the flaw is having never considered the question.
"Threads? Clock?" Callers are multi-threaded; assume single-threaded state for the design round and say so. A monotonic clock is available — worth asking, because an algorithm that reads wall-clock time breaks when time jumps.
The requirement set this chapter builds against
Limit shape N per window (default 100/minute)
Burst policy defined by the chosen algorithm — candidate's call,
defended (lesson 02 chooses token bucket)
Keys opaque strings; millions of them; memory bounded
Config library-wide default + per-key-pattern overrides
(premium tier gets more); stated resolution order
allow() synchronous, non-blocking, hot path, returns a
decision — never sleeps
Denials may carry a retry-after hint if cheap to compute
Failure posture chosen per use case; defended, not defaulted
Threading single-threaded state (stated assumption)
Clock monotonic time source available
And the standing caveat: a live interviewer's answers may differ — sliding-window semantics, different override schemes. The questions and the reasons behind them transfer; this answer sheet is just the one we build against.
Key takeaway
The rate limiter's scoping act has a twist: the burst question comes back to you, so the requirements conversation includes owning an algorithm choice. Around it, pin the allow() contract (sync, non-blocking, hot-path), the millions-of-keys memory constraint, config override resolution, the retry-hint decision, and a defended failure posture. Every one of those is a design input, and interviewers grade whether you collected them before designing.