Chaturmind
LearnDSASystem DesignDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

Β© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← System Design Interview Playbook

Interview Framework

  • The 6-Step Design Framework
  • HLD Fundamentals Refresher
  • Requirement Gathering Practice
  • Domain Decomposition
  • API Contract Design
  • Data Ownership Model
  • Failure Scenario Walkthroughs
  • Architecture Diagramming
  • Back-of-Envelope Estimation

10 Case Studies

  • Design a URL Shortener
  • Design Twitter / X
  • Design WhatsApp
  • Design Netflix
  • Design a Rate Limiter
  • Design a Search Autocomplete
  • Design a Distributed Cache
  • Design a Notification Service
  • Design Uber / Ride Sharing
  • Design a Distributed File Storage System
  • Design a Distributed Task Scheduler
  • Design a Message Queue System
  • Design an Authentication System at Scale
  • Design a Distributed Logging & Metrics Pipeline
  • Design a Food Delivery Platform
  • Design a Real-Time Analytics Dashboard
  • Design a Monitoring & Alerting System
  • Design Container Orchestration Basics
  • Design a CI/CD Pipeline System
  • Design Service Mesh Basics
  • Design a Centralized Configuration & Secrets System
  • Design a Batch Processing System
  • Design a Data Warehouse / Analytics Storage Layer
  • Design Global Content Delivery
  • Case studies

    πŸ—οΈDesign a URL Shortener
  • πŸ—οΈDesign a Rate Limiter
  • πŸ—οΈDesign Twitter / X
  • πŸ—οΈDesign WhatsApp
  • πŸ—οΈDesign Netflix
  • πŸ—οΈDesign a Distributed Cache
  • πŸ—οΈDesign a Notification Service
  • πŸ—οΈDesign a Search Autocomplete System
  • πŸ—οΈDesign Uber / Ride Sharing
  • πŸ—οΈDesign a Web Crawler
  • πŸ—οΈDesign a Payment System
  • πŸ—οΈDesign a Distributed Lock Service
  • πŸ—οΈDesign a Video Streaming Platform
  • πŸ—οΈDesign a Search Engine
  • πŸ—οΈDesign E-Commerce Checkout & Inventory at Scale
Chaturmind
← System Design Interview Playbook

Interview Framework

  • The 6-Step Design Framework
  • HLD Fundamentals Refresher
  • Requirement Gathering Practice
  • Domain Decomposition
  • API Contract Design
  • Data Ownership Model
  • Failure Scenario Walkthroughs
  • Architecture Diagramming
  • Back-of-Envelope Estimation

10 Case Studies

  • Design a URL Shortener
  • Design Twitter / X
  • Design WhatsApp
  • Design Netflix
  • Design a Rate Limiter
  • Design a Search Autocomplete
  • Design a Distributed Cache
  • Design a Notification Service
  • Design Uber / Ride Sharing
  • Design a Distributed File Storage System
  • Design a Distributed Task Scheduler
  • Design a Message Queue System
  • Design an Authentication System at Scale
  • Design a Distributed Logging & Metrics Pipeline
  • Design a Food Delivery Platform
  • Design a Real-Time Analytics Dashboard
  • Design a Monitoring & Alerting System
  • Design Container Orchestration Basics
  • Design a CI/CD Pipeline System
  • Design Service Mesh Basics
  • Design a Centralized Configuration & Secrets System
  • Design a Batch Processing System
  • Design a Data Warehouse / Analytics Storage Layer
  • Design Global Content Delivery
  • Case studies

    πŸ—οΈDesign a URL Shortener
  • πŸ—οΈDesign a Rate Limiter
  • πŸ—οΈDesign Twitter / X
  • πŸ—οΈDesign WhatsApp
  • πŸ—οΈDesign Netflix
  • πŸ—οΈDesign a Distributed Cache
  • πŸ—οΈDesign a Notification Service
  • πŸ—οΈDesign a Search Autocomplete System
  • πŸ—οΈDesign Uber / Ride Sharing
  • πŸ—οΈDesign a Web Crawler
  • πŸ—οΈDesign a Payment System
  • πŸ—οΈDesign a Distributed Lock Service
  • πŸ—οΈDesign a Video Streaming Platform
  • πŸ—οΈDesign a Search Engine
  • πŸ—οΈDesign E-Commerce Checkout & Inventory at Scale
HomeLearnSystem DesignSystem Design Interview Playbook10 Case Studies
βœ“ FreeIntermediateΒ· 7 min read

Design a Rate Limiter

How to run a distributed rate limiter design in a 45-minute interview: algorithm trade-offs, an atomic Redis Lua token bucket, hot keys, and fail-open vs fail-closed.

Published September 21, 2026


Design a Rate Limiter β€” the 45-minute interview walkthrough

This lesson is the interview version: how to present the design, the algorithms to compare, and the distributed-systems details interviewers probe. The full reference design is the case study Design a Rate Limiter in this chapter. For the class-level version (implementing a limiter inside one process), see In-Memory Rate Limiter in the LLD course.

A rate limiter caps how many requests a client may make in a period ("100 requests per minute per API key"). It protects services from abuse and accidental overload, keeps one noisy customer from starving others, and enforces pricing tiers. It sits on the path of every request, so it must be fast (well under a millisecond or two), accurate enough, and must fail safely when its own storage has problems.

Minutes 0–5: clarify requirements

  • Who is limited? Per user, per API key, per IP (for anonymous traffic), per endpoint, or a combination?
  • What limits? A per-second rate, per-minute or per-day quotas, and are short bursts acceptable?
  • Where does it run? In the API gateway (common), as a library inside each service, or as a separate service?
  • Scale: e.g. 1 million requests/second across 50 gateway nodes, so the counters must be shared between nodes.
  • When the limiter's store is down, do we let traffic through (fail open) or block it (fail closed)?
  • Client experience: return 429 Too Many Requests with Retry-After and X-RateLimit-* headers.

Minutes 5–15: the high-level design

 client ──▢ API gateway ──▢ rate-limit check ──allow──▢ backend service
                               β”‚   β–²
                               β–Ό   β”‚ atomic "check and consume" (one round trip)
                          Redis cluster (counters/buckets, sharded by key)
                               β–²
                  rules config (limits per plan/endpoint), cached in each gateway

Each request builds a key such as rl:{apiKey}:{endpoint}, runs one atomic check against Redis, and either continues or returns 429. The rules (who gets which limit) are loaded from configuration and cached locally, so they aren't fetched per request.

Minutes 15–35: the deep dives

1. Choosing the algorithm

AlgorithmHow it worksProsCons
Fixed window counterCount requests per clock window (e.g. per minute)Tiny state, one INCRAllows 2Γ— the limit across a window boundary (100 at 0:59 + 100 at 1:00)
Sliding window logKeep every request timestamp and count those in the last 60 sExactMemory per request, which is expensive at high limits
Sliding window counterCurrent window's count + previous window's count Γ— the fraction of overlapNear-exact with two countersAn approximation (assumes even spread within the previous window)
Token bucketA bucket of capacity C refills at rate r; each request takes a tokenAllows controlled bursts up to C while enforcing the average r; tiny stateTwo values to update atomically
Leaky bucketRequests queue and drain at a constant ratePerfectly smooth outputAdds queueing delay; bursts wait instead of being served

The usual recommendation: a token bucket for API limits (bursty clients are normal, and the average rate is what matters), or a sliding window counter when the product wants "N per minute" semantics without the boundary problem.

2. Atomicity across many gateway nodes

The check "read current state, decide, update" must be atomic. Otherwise two nodes both read "99 used" and both allow request 100. In Redis, run the whole decision as one Lua script, which executes atomically on the server in a single round trip. A token bucket:

-- KEYS[1] = bucket key
-- ARGV[1] = capacity, ARGV[2] = refill rate (tokens per second), ARGV[3] = now (ms), ARGV[4] = tokens requested
local capacity = tonumber(ARGV[1])
local rate     = tonumber(ARGV[2])
local now      = tonumber(ARGV[3])
local want     = tonumber(ARGV[4])

local state  = redis.call('HMGET', KEYS[1], 'tokens', 'ts')
local tokens = tonumber(state[1]) or capacity           -- new client starts with a full bucket
local ts     = tonumber(state[2]) or now

tokens = math.min(capacity, tokens + (now - ts) / 1000 * rate)   -- refill for the time elapsed

local allowed = 0
if tokens >= want then
  tokens = tokens - want
  allowed = 1
end

redis.call('HSET', KEYS[1], 'tokens', tokens, 'ts', now)
redis.call('PEXPIRE', KEYS[1], math.ceil(capacity / rate * 1000) * 2)   -- idle buckets expire
return { allowed, math.floor(tokens) }                                    -- remaining β†’ X-RateLimit-Remaining

The gateway passes now from its own clock. With many gateway nodes, their clocks differ slightly. That's acceptable for rate limiting, and you can use Redis's TIME command inside the script if tighter accuracy matters. Because each client's key lives on one Redis shard, the cluster scales horizontally by key.

3. Latency and hot keys

  • One Redis round trip per request is usually well under a millisecond inside the same data centre.
  • For extremely high-volume keys (one huge customer), a local token bucket per gateway node with a share of the global limit (limit Γ· number of nodes), periodically rebalanced, avoids hammering one Redis key. It trades some accuracy for throughput.

4. Failure handling

If Redis is unreachable, the limiter must decide quickly (tight timeout) and fail open for most APIs, meaning allow the request and log it, because blocking all traffic turns a limiter outage into a full outage. Sensitive endpoints (login, OTP, payments) can fail closed, or fall back to a conservative local in-memory limiter.

Minutes 35–45: trade-offs and wrap-up

  • Accuracy vs cost: exact sliding logs are expensive; counters and buckets are approximate but cheap.
  • Central store vs local limits: shared Redis gives global accuracy, local buckets give speed and resilience, and hybrids get most of both.
  • Where to enforce: the gateway protects everything behind it and stops abuse early. Services may add their own, finer limits.
  • With more time: tiered plans configured dynamically, per-endpoint cost weights (an expensive search counts as 5 tokens), and distributed denial-of-service protection at the edge or CDN, which is a different layer from per-client API limits.

Common mistakes in interviews

  • Read-then-write in two Redis calls, a race between gateway nodes.
  • Fixed windows without acknowledging the boundary burst.
  • Storing a timestamp per request for high limits without discussing memory.
  • No decision on fail open vs fail closed.
  • Forgetting the client contract: 429, Retry-After and remaining-quota headers.

Follow-up questions this topic invites β€” and their answers

Q: Token bucket or leaky bucket? A: Token bucket allows bursts up to the bucket size while enforcing an average rate, and requests are served immediately if tokens are available. That suits APIs. Leaky bucket smooths traffic to a constant outflow by queueing, which suits protecting a downstream system that can't absorb bursts. It adds latency.

Q: How do you rate-limit across multiple data centres? A: Either split the global limit between regions (each enforces its share locally, and shares are adjusted periodically from observed traffic), or accept per-region limits. Synchronizing one global counter across regions on every request adds too much latency. Exactness is traded for speed.

Q: How would you limit by IP when many users share one IP (offices, mobile carriers)? A: Use IP limits only as a coarse safety net for anonymous traffic, with generous thresholds, and prefer authenticated identities (user, API key) for real limits. Combine signals (IP + device + account) for abuse detection rather than relying on IP alone.

Q: Why use a Lua script instead of MULTI/EXEC? A: A Redis transaction (MULTI/EXEC) can't make decisions based on values read inside it. The logic needs "read tokens, compute refill, compare, then write". A Lua script runs that entire read-compute-write atomically on the server, in one network round trip.

Q: What should the client do on a 429? A: Honour Retry-After (or back off exponentially with jitter), and not retry immediately in a tight loop. Well-behaved SDKs do this automatically. Servers should make the headers accurate so clients can pace themselves.

Previous

Design Netflix

Next

Design a Search Autocomplete

AI Tutor

Lesson: Design a Rate Limiter

Quick actions

AI responses can be inaccurate. Verify critical information.