Interview replay

Full round replay — rate limiting

10 minintermediate215 yrs

Timed verbal replay with pass/fail criteria per follow-up.

How to run this

Answer out loud, timed. Do not read the entry first. Then compare against "The Answer" and "Interviewer's Next Move" and mark yourself.

The opener

How would you implement a rate limiter?

Budget: 45 seconds. Going long here is itself a fail signal.

Follow-ups

  1. 1. “Why not just a counter you reset every minute?

    Testing: Whether they know the boundary burst.

    Scoring

    Pass: That is a fixed window, and it allows twice the limit across a boundary — the full limit in the last instant of one minute and again in the first instant of the next. Clock-aligned traffic lands exactly there: cron jobs, retry backoffs that round to the minute, dashboards refreshing.

    Fail: Treats it as correct, or calls the burst a rare edge case.

  2. 2. “Token bucket or leaky bucket?

    Testing: Whether the distinction is real to them.

    Scoring

    Pass: Token bucket allows a burst up to the bucket size and limits the average rate; leaky bucket drains at a fixed rate and smooths output completely. Token for public APIs where clients are allowed to be bursty, leaky when a downstream system needs an even feed.

    Fail: Says they are the same, or cannot say what changes.

  3. 3. “Does the token bucket need a background thread to refill it?

    Testing: A common misunderstanding that costs a lot at scale.

    Scoring

    Pass: No. The refill is computed from elapsed time when a request arrives, so idle keys cost nothing. A timer per key would be a million scheduled tasks for a million users.

    Fail: Describes a scheduler topping up buckets.

  4. 4. “How much memory does each algorithm need per key?

    Testing: Whether they can rule one out with a number.

    Scoring

    Pass: Fixed window and token bucket are constant. A sliding log stores one timestamp per request in the window — a limit of 10,000 with a million users is ten billion timestamps. A sliding counter is two integers and tracks the exact algorithm's rate to about a percent.

    Fail: Recommends the sliding log for a large key count without costing it.

  5. 5. “Now run it on ten instances.

    Testing: The real question.

    Scoring

    Pass: An in-memory limiter becomes a limit ten times higher, and it loosens further every scale-out — so protection weakens exactly when load is highest. The counter has to be shared: Redis with an atomic script, or the gateway.

    Fail: Says it still works, or has not considered it.

  6. 6. “Why not just divide the limit by the number of instances?

    Testing: Whether they see the hidden assumption.

    Scoring

    Pass: It assumes even routing, and nothing enforces that — sticky sessions, hashing on user id, or an instance restarting all skew it. Under skew a customer inside their quota gets refused with no error logged anywhere. It also has to be re-tuned on every scale event.

    Fail: Offers it as the answer with no caveat.

  7. 7. “Why must the distributed check be atomic?

    Testing: Read-modify-write under concurrency.

    Scoring

    Pass: Otherwise two instances read the same count and both allow. A Lua script, INCR with an expiry, or a CAS loop makes it indivisible. Without it the limiter is approximately right, which for a paid quota is wrong.

    Fail: Proposes a GET followed by a SET.

  8. 8. “Redis is down. What does your limiter do?

    Testing: Whether they know it is a decision.

    Scoring

    Pass: It is a choice, per endpoint. Fail open keeps the API up and drops the protection during exactly the incident you need it in; fail closed keeps the guarantee and turns a Redis blip into an outage. Fail closed with a generous local fallback for anything protecting a fragile backend, fail open with an alert for public reads.

    Fail: Has no answer, or assumes the store is always available.

  9. 9. “What key do you count against?

    Testing: A defect that reaches production often.

    Scoring

    Pass: Whatever the limit is sold against — usually the API key or tenant. Source IP puts every customer behind one NAT gateway on a shared quota. IP is still useful as a separate, coarser layer against volumetric abuse, but it is a different limit.

    Fail: Says IP without qualification.

  10. 10. “A million users hit your service once each and never come back. What happens to memory?

    Testing: The leak nobody tests for.

    Scoring

    Pass: Without an expiry the key map retains all of them — on a public endpoint that is attacker-controlled growth. Use a cache with expiry and a max size, or PEXPIRE in Redis. The expiry has to exceed the window, because an evicted key returns with a full bucket and that is a free burst.

    Fail: Does not consider eviction, or expires at exactly the window.

  11. 11. “What do you return to the client?

    Testing: The detail that separates a working limiter from an outage report.

    Scoring

    Pass: 429 with Retry-After, plus RateLimit-Limit, -Remaining and -Reset so a well-behaved client can pace itself instead of discovering the limit by hitting it. Not 503 — that says the service is broken and invites retries against something healthy.

    Fail: Says 503, or returns 429 with no headers.

Score yourself

11/11 — you can design and defend this in a system design round 8-10 — solid; the distributed and fail-open answers are where points go 5-7 — you know the algorithms but not the deployment; redo the Production tier 0-4 — run the Warm-up and break a fixed window yourself

← Back to How would you implement a rate limiter?