Backend & Node.js · APIs, data and failure
How do you design rate limiting for an API?
Short answer
Choose an algorithm such as fixed window, sliding window, token bucket or leaky bucket, key it by user, IP or API key, and enforce it atomically in a shared store. Return 429 with Retry-After and document the limits.
Rate limiting protects availability, enforces fair use and limits abuse. The algorithm determines burst behavior. Fixed windows are simple but allow a burst at the boundary. Token bucket allows controlled bursts up to the bucket size while enforcing an average rate.
function allow(bucket, now) {
const elapsed = now - bucket.updatedAt;
bucket.tokens = Math.min(
bucket.capacity,
bucket.tokens + elapsed * bucket.refillPerMs,
);
bucket.updatedAt = now;
if (bucket.tokens < 1) return false;
bucket.tokens -= 1;
return true;
}Design decisions
- Key by identity when possible, not only by IP, because NAT shares addresses
- Use different limits for different endpoints and plans
- Store counters in Redis with an atomic script or a single command
- Return 429 with Retry-After and a clear error body
- Consider a local in-process limiter plus a shared limiter to reduce Redis load
- Exclude health checks and protect internal callers deliberately
The distributed version is the hard part. A read, compare and write in application code races across instances. Use a Lua script or a data structure that performs the increment and expiry atomically. Also decide whether limits are per second, per minute or a quota, and how the client discovers them.
Follow-ups you should expect
Interviewers use these to check whether the first answer was understanding or memorisation.
- 1What is the difference between a token bucket and a fixed window?
- 2How do you implement an atomic limiter across instances?
- 3Why is limiting by IP alone unreliable?
- 4What should a 429 response include?
Say it in the interview
I pick an algorithm based on burst behavior: fixed or sliding windows for simple quotas, token bucket when limited bursts are acceptable. I key by user or API key where possible and enforce the counter atomically in a shared store such as Redis. I return 429 with Retry-After and document the limits. The race across instances is the main implementation risk, so I never do a non-atomic read-modify-write.
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 Backend & Node.js mock interview and get scored feedback on depth, clarity and confidence.