In short
Interviewers love 'design a rate limiter' because it starts simple and gets hard fast: many servers, shared counters, race conditions, and what to do when Redis itself is down.
Another classic system design question — designing a rate limiter that works correctly and efficiently across multiple distributed server instances, not just a single process.

Think of it like this
The metro turnstile guard
During rush hour, a guard at the metro gate lets in a set number of people per minute, counting with a clicker. All gates report to one control room, so you can't dodge the limit by switching gates. Your rate limiter is that guard, and Redis is the control room.
Watch it happen
A shared counter in Redis
GET /search (key k_7)
1/7A request arrives with API key k_7. The limit is 100 requests per minute per key.
The big ideas
What you need to know
Shared state
With many API servers, counters must live in a shared store like Redis, or each server allows the full limit.
Atomic operations
INCR is atomic, so two servers can't both read 99 and write 100. Use Lua scripts for multi-step logic.
Limit by what matters
Per API key, user, IP or endpoint. A login endpoint needs much stricter limits than a product list.
Decide the failure mode
If Redis is down, fail open (allow) or fail closed (block)? Often open for reads, closed for logins.
See it in code
Walk through the code
1const LIMIT = 100;2 3export async function rateLimit(req, res, next) {4 const key = req.get("x-api-key") ?? req.ip;5 const minute = Math.floor(Date.now() / 60_000);6 const redisKey = `rl:${key}:${minute}`;7 8 const [count] = await redis.multi().incr(redisKey).expire(redisKey, 60).exec();9 res.set("RateLimit-Limit", String(LIMIT));10 res.set("RateLimit-Remaining", String(Math.max(0, LIMIT - Number(count))));11 if (Number(count) > LIMIT) {12 res.set("Retry-After", String(60 - (Math.floor(Date.now() / 1000) % 60)));13 return res.status(429).json({ error: "Too many requests" });14 }15 next();16}1/4Identify the client and build a counter key for the current minute.
This is a fixed window, the simplest design. Interviews usually move on to sliding windows or a token bucket kept in Redis with a Lua script.
Save yourself the pain
Common mistakes
Oops
Keeping counters in each server's memory behind a load balancer.
Do this instead
With 4 servers, a client effectively gets 4 times the limit. Use a shared store.
Oops
Doing GET, checking in code, then SET, as separate calls.
Do this instead
Two requests can read the same count at once and both pass. Use INCR or a Lua script so it's atomic.
Your mission
A distributed limiter
Quick check
Why use Redis INCR instead of GET followed by SET for the counter?
Go deeper
- 1Design how a rate limiter's counters would be shared across multiple server instances using Redis.
- 2Decide which algorithm (token bucket, sliding window) fits the requirements given and why.
- 3Consider what happens to your rate limiter if Redis itself becomes unavailable.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.