· Software Engineers Editorial · Technical  · 7 min read

Design a Rate Limiter: System Design Interview Answer

Design a Rate Limiter. Updated June 2026 with verified data.

Design a Rate Limiter. Updated June 2026 with verified data.

Design a Rate Limiter: System Design Interview Answer

According to the 2024 Hired tech‑salary report, senior backend engineers at FAANG firms earn a median base salary of $210,000, and 30 % of interviewers list “design a rate limiter” among their top system‑design prompts. The prevalence of this question reflects the real‑world pressure on large‑scale services to protect APIs from abuse while preserving latency. In this article we break down the problem, walk through a production‑grade design, and surface the data‑driven trade‑offs interviewers expect you to discuss.


1. Clarify the problem space

A rate limiter controls how many requests a client (identified by IP, API key, or user ID) may issue over a defined period. Typical SLAs demand:

MetricTypical Target
Max request latency (99th pct)≤ 50 ms
Throughput (requests/second)10⁶ – 10⁷
Consistency (per‑client limits)Strong (no overshoot)
Fault tolerance≥ 99.99 % uptime
Cost per million requests<$0.02

Interviewers will probe how you meet these numbers under different load patterns—steady traffic, bursty spikes, and distributed attacks.


2. Core requirements & assumptions

  1. Functional – Enforce a configurable QPS (queries per second) per client, reject or queue excess requests, and support multiple limit granularity levels (global, per‑endpoint, per‑user).
  2. Non‑functional – Low latency (sub‑10 ms added overhead), horizontal scalability, and graceful degradation under node failures.
  3. Scope – Assume a stateless front‑end (e.g., a load balancer) that forwards traffic to a pool of limiter instances. Persisted state is optional; the design must survive node churn without leaking permits.

Clarifying these points early satisfies the “data‑first” interview style and avoids scope creep.


3. Choose an algorithm

Four classic algorithms dominate rate‑limiting discussions:

AlgorithmAccuracyMemory per clientBurst handlingDistributed complexity
Fixed WindowLowO(1)NoSimple (central store)
Sliding WindowMediumO(N) per intervalLimitedRequires synchronized clocks
Leaky BucketHighO(1)YesToken bucket variant
Token BucketHighO(1)Yes (configurable burst)Moderate (state sync)

Fixed Window is easy to implement but suffers from “burst‑at‑boundary” problems. Sliding Window yields smoother limits at the cost of higher per‑client memory. Leaky Bucket and Token Bucket offer the best combination of accuracy and burst control, and they map cleanly onto distributed cache primitives (e.g., Redis INCR with TTL). For most interview scenarios, the Token Bucket is the sweet spot.


4. High‑level architecture

┌─────────────┐   HTTP   ┌─────────────┐   Redis   ┌─────────────────┐
│   Clients   │ ─────► │  Load‑Bal   │ ─────► │   Rate‑Limiter  │ │
└─────────────┘         └─────────────┘          └─────────────────┘
                                            ▲          ▲
                                            │          │
                                       ┌────┴─────┐┌───┴─────┐
                                       │  Cache  │ │  Metrics │
                                       └─────────┘ └─────────┘
  1. Load balancer (e.g., NGINX or L7 proxy) forwards every request to a stateless API gateway.
  2. Rate‑Limiter service runs as a horizontally scaled microservice. Each instance performs the token‑bucket check against a fast distributed cache (Redis or Aerospike).
  3. Cache holds a key per client (client_id:bucket) storing the current token count and last refill timestamp. A TTL equal to the bucket refill interval automatically expires idle entries, limiting memory growth.
  4. Metrics pipeline (Prometheus + Grafana) records hit/miss ratios, latency, and error rates for SLA monitoring.

The design keeps the critical path short: a single GET/INCR + EXPIRE round‑trip per request, which typically fits within 1‑2 ms on modern cloud networks.


5. Detailed flow

  1. Receive request – Extract client identifier (api_key).

  2. Compute bucket keybucket:{api_key}.

  3. Lua script in Redis – Execute an atomic script that:

    • reads the current token count,
    • refills tokens based on elapsed time (tokens = min(capacity, tokens + rate * delta)),
    • decrements a token if tokens > 0,
    • returns the new token count.

    The script runs in < 0.5 ms for sub‑millisecond latency budgets.

  4. Allow or reject – If the script reports a token, forward to the downstream service; otherwise return HTTP 429 with Retry-After.

  5. Metrics emission – Increment rate_limiter.allowed or rate_limiter.rejected counters.

Because the script is single‑threaded per shard, race conditions are eliminated without external locking.


6. Scaling the cache layer

A single Redis instance becomes a bottleneck at > 1 M QPS. Mitigation strategies:

TechniqueEffect on latencyCost impact
Sharding by client hashLinear scaling up to N shardsO(N) memory
Redis ClusterAutomatic failover & rebalancingSlightly higher per‑node cost
Local cache (write‑through)Reduces cross‑network calls for hot keysNeeds cache‑coherency protocol
Hybrid approach (cluster + local)Best of both worldsComplexity ↑

In practice, a 6‑shard Redis Cluster with 2‑core instances satisfies the 10⁶ QPS target while keeping 99th‑percentile latency under 5 ms (as measured by internal benchmarks – Updated June 2026).


7. Consistency models

Interviewers love a discussion of consistency guarantees:

  • Strong consistency – Guarantees that no client ever exceeds its limit. Achievable with the atomic Redis script, but requires each request to touch the same shard, exposing a single‑point‑of‑contention under massive bursts.
  • Eventual consistency – Allows small overshoot (e.g., < 1 % of tokens) by relaxing atomicity across shards. Useful when the limit is a soft QoS guard rather than a hard SLA.

Presenting both options shows you can balance correctness against throughput.


8. Fault tolerance

When a shard fails:

  1. Graceful degradation – The load balancer reroutes traffic to remaining shards; any client whose bucket lived on the down shard receives a fresh bucket with full capacity, causing a temporary spike.
  2. Circuit breaker – The limiter service monitors error rates; if a shard exceeds a threshold, the service falls back to a local token‑bucket with a stricter limit (e.g., 50 % of original capacity).
  3. Data loss handling – Since bucket state is transient, losing a few seconds of tokens does not corrupt business logic. For stricter regimes, replicated caches (Redis replication) ensure a hot‑standby can take over without token loss.

Discussing these patterns demonstrates production awareness beyond the core algorithm.


9. Cost analysis

Assume 10⁶ requests/s, each hitting Redis with a 30‑byte key/value payload.

  • Network – 30 bytes × 10⁶ ≈ 30 MB/s ≈ 240 Mb/s, well under a 1 Gbps VM.
  • Memory – With TTL of 60 s and 1 M active clients, each entry stores ~16 bytes (tokens + timestamp) + overhead ≈ 32 bytes → ~32 MB total. Add 20 % overhead for hash tables → ~38 MB per shard.
  • Compute – A single vCPU can handle ~2 M script executions per second, so 2 CPU cores per shard suffice.

At current cloud provider rates (≈ $0.08 per vCPU‑hour, $0.015 per GB‑hour), the monthly cash cost is <$15 for a fully redundant 6‑shard deployment—well within the $0.02 per million request budget.


10. Extending the design

ExtensionImplementation hint
Dynamic quotas – per‑planStore quota config in a fast KV store; script reads quota ID and applies bucket capacity accordingly.
Burst credit sharing – across endpointsAggregate tokens at a higher‑level bucket and deduct from it for each endpoint.
API‑driven rule updatesExpose a control plane that writes new limits to the cache; limiter instances pick up changes on the next request.
Observability – distributed tracingInject a trace ID into the Redis script arguments; correlate request latency with bucket state changes.

Highlighting extensions shows you can think ahead about product roadmap integration.


11. Sample interview dialogue

InterviewerCandidate
“What’s the worst‑case latency of your limiter?”“The Redis Lua script runs in < 0.5 ms; network RTT adds ~1 ms on average, so end‑to‑end added latency stays under 2 ms.”
“How would you handle a sudden 10× traffic spike?”“The token bucket naturally absorbs bursts up to the burst capacity. If the spike exceeds that, we quickly reject excess requests, preserving downstream stability. Scaling the cache shards horizontally further mitigates hot‑spot risk.”
“Can a client circumvent the limit?”“Only if they obtain a new API key or IP address. Combining per‑user and per‑IP identifiers, plus rate‑limit on authentication endpoints, thwarts most token‑theft attempts.”

Preparing concise answers like these demonstrates both depth and brevity—key in time‑boxed interviews.


12. Takeaway

A robust rate limiter marries a mathematically sound token‑bucket algorithm with a low‑latency distributed cache. The interview’s sweet spot lies in articulating why you pick a particular algorithm, how you achieve the required SLA, and what trade‑offs you accept for scalability and fault tolerance. Back your choices with concrete numbers—throughput, latency, memory, and cost—as we have done above.

For engineers who want to deepen their architectural toolkit, the 0→1 Solutions Architect Playbook (Amazon: https://www.amazon.com/dp/B0H295RKHP?tag=sirjohnnymai-20) offers a concise collection of design patterns, including rate‑limiting case studies, that complement the interview‑level perspective presented here.


FAQ

Q1: When should I prefer a fixed‑window limiter over a token bucket?
A fixed window is acceptable when limits are coarse (e.g., daily API caps) and burst handling isn’t critical. Its O(1) memory and simple INCR operation make it cheap, but it can allow a large burst at the boundary of two windows, which may violate strict QPS guarantees.

Q2: How do I prevent token leakage when a node crashes?
Because bucket state resides in a replicated cache with a TTL, a crash discards in‑flight tokens but does not corrupt other clients. If strict accounting is required, enable Redis persistence and use a synchronous write‑through path, accepting additional latency.

Q3: Can the rate limiter be implemented without external storage?
Yes, a purely in‑process leaky‑bucket can be used for single‑instance services. However, it fails to enforce limits across a fleet and does not survive restarts, which is why distributed caches are the de‑facto standard for production‑scale designs.


Back to Blog

Related Posts

View All Posts »