System Design · Architecture under constraints

Design a rate limiter for a public API.

Mediumrate limitertoken bucketredisUpdated Oct 2026
Quiz this track

Short answer

Define limits per API key and endpoint, enforce token buckets atomically in Redis, propagate a request id, and return 429 with retry metadata. Plan for hot keys and limiter outages.

Assume 50,000 requests per second at peak, limits per API key and per endpoint, a burst allowance, and a requirement that one abusive tenant cannot degrade others. The limiter must add only a few milliseconds to the request path and must be observable.

Token bucket

text
on request:
  refill tokens by elapsed_time * rate
  cap tokens at burst_size
  if tokens >= cost:
    tokens -= cost
    allow
  else:
    reject with retry_after = (cost - tokens) / rate
The bucket allows a burst up to its size while enforcing a sustained refill rate.

The counter must be updated atomically. A read, compute and write across separate Redis calls races when many requests arrive together. Use a Lua script or a single conditional operation so the refill, check and decrement happen as one step. The key should include the tenant and the limit dimension, such as ratelimit:tenant:endpoint:minute.

Distribute enforcement. A gateway can pre-check a local token bucket to shed obvious excess, then confirm with the shared store. For very large tenants, shard the counter and sum the shards, accepting a small over-admission window. Set a short TTL on idle keys so memory does not grow without bound.

Decide the failure policy before an incident. If Redis is unavailable, failing closed protects the backend but rejects legitimate traffic; failing open preserves availability but removes protection. Most public APIs choose a degraded local limit and an alert. Return 429 with Retry-After, and include limit and remaining headers so clients can adapt.

Clock skew affects window boundaries and refill calculations. Use the shared store's time or a monotonic source where possible. Test the limiter under concurrency, not just with sequential requests.

Follow-ups you should expect

Interviewers use these to check whether the first answer was understanding or memorisation.

  1. 1Why is a Lua script needed for the counter update?
  2. 2How do you limit a tenant whose traffic exceeds one Redis shard?
  3. 3What should the limiter do if Redis is unreachable?
  4. 4How do you avoid penalizing legitimate bursts?

Say it in the interview

I would use token buckets keyed by tenant and endpoint, enforced atomically in Redis with a Lua script or a single conditional operation. A local pre-check at the gateway sheds obvious excess, and very large tenants get sharded counters. I decide explicitly whether an outage fails open or closed, and I return 429 with Retry-After and quota headers. The limiter also needs metrics and concurrency tests, because races are the main correctness risk.

Use this as a model, not a script. Saying it in your own words is what makes it survive follow-up pressure.

Related questions

Reading is step one

Now say it out loud.

Understanding an answer and delivering it under pressure are different skills. Rehearse this topic in a live System Design mock interview and get scored feedback on depth, clarity and confidence.