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 Design Interview Playbook10 Case Studies
Rate Limiter

Design a Rate Limiter

redisalgorithmsapi-gatewaydistributed-systems

Problem Statement

Design a rate limiter that restricts the number of requests a client can make to an API within a time window. Support multiple limiting strategies and work correctly in a distributed environment.

Requirements

Functional

  • ✓Limit requests per user/IP per time window (e.g., 100 req/min)
  • ✓Return HTTP 429 with Retry-After header when limit exceeded
  • ✓Support multiple algorithms: token bucket, sliding window
  • ✓Rules configurable per endpoint and user tier

Non-Functional

  • ✓Decision latency under 5ms
  • ✓Accurate across multiple API server instances
  • ✓No single point of failure

Capacity Estimation

Capacity Estimation

For 10M users, each making up to 100 req/min:

  • Peak QPS: 10M × 100 / 60 ≈ 16.7M requests/sec
  • Counter storage: 10M users × 1 counter × 8 bytes ≈ 80 MB in Redis — trivially fits in memory

High-Level Architecture

Architecture

API Request
    │
    ▼
[Rate Limiter Middleware]
    │  checks Redis
    ├── Allowed → forward to API handler
    └── Denied  → 429 Too Many Requests

[Redis Cluster]
  key: ratelimit:{userId}:{window}
  value: request count
  TTL: window size

Algorithm comparison (implementation level)

  • Token bucket: a bucket holds up to N tokens, refilled at a fixed rate; each request consumes one token, rejected if the bucket is empty. Allows bursts up to the bucket size, simple to reason about — the most commonly used approach in practice.
  • Sliding window log: stores a timestamp per request in a sorted structure; a request is allowed if the count of timestamps within the trailing window is under the limit. Perfectly accurate, but memory cost grows with request volume (one entry per request in the window).
  • Sliding window counter: approximates the sliding log using two fixed windows (current + previous) and a weighted count between them — far cheaper than storing every timestamp, with a small, bounded accuracy tradeoff.
  • Fixed window counter: simplest — a counter per fixed time window (e.g. per-minute), reset at each boundary. Cheapest, but allows up to 2x the limit in a short burst spanning a window boundary (all requests at the end of one window plus all at the start of the next).

Distributed rate limiting with Redis: making the check-and-increment atomic

A naive GET count then INCR from the application has a race window between two app servers checking simultaneously — both could read "under limit" before either increments. Two fixes:

INCR ratelimit:{userId}:{window}
EXPIRE ratelimit:{userId}:{window} <window_seconds>   -- only on first increment

INCR itself is atomic in Redis, but the INCR + conditional EXPIRE pair is not — the standard fix is a Lua script, which Redis executes atomically as a single operation:

local current = redis.call("INCR", KEYS[1])
if current == 1 then
  redis.call("EXPIRE", KEYS[1], ARGV[1])
end
return current

Running this via EVAL guarantees the increment-and-maybe-expire happens as one atomic unit, closing the race window entirely.

API Design

Integration

The rate limiter is middleware, not an API itself. Response headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1735689600
Retry-After: 37    (only on 429)

Database Design

Redis Data Model

Fixed window counter:

SET ratelimit:user123:1735689600 0 EX 60
INCR ratelimit:user123:1735689600

Sliding window log (more accurate, more memory):

ZADD ratelimit:user123 <timestamp> <requestId>
ZREMRANGEBYSCORE ratelimit:user123 0 <now - window>
ZCARD ratelimit:user123

Scaling Strategy

Use a Redis Cluster to distribute the keyspace. Each rate limiter instance talks to the same Redis cluster, so limits are enforced globally across all API servers.

Trade-offs

  • Fixed window is simple but allows burst at window boundary (50 req at end of window + 50 at start of next = 100 in 2 seconds).
  • Sliding window log is accurate but uses more memory (one entry per request vs. one counter).
  • Token bucket smooths bursts and is the most flexible — used by most cloud providers.

Where to enforce the limit: client SDK vs gateway vs per-service

  • Client SDK: cheapest (no network hop to check), but trivially bypassable — a malicious or buggy client can simply not enforce it. Only useful as a courtesy/UX signal, never as the actual control.
  • API gateway: enforced centrally, before any backend service sees the request — the most common choice for a system-wide limit, since it's a single enforcement point that can't be bypassed by any individual service.
  • Per-service: allows different limits per service/endpoint (a search endpoint might need a stricter limit than a health check), at the cost of duplicated enforcement logic and each service needing its own path to the shared rate-limit store (e.g. Redis).

Production systems commonly layer these: a coarse gateway-level limit as the primary defense, with finer per-service limits for specific expensive endpoints.

Multi-tenant / per-API-key dynamic rules

A production API gateway rarely enforces one global limit — different tenants (or pricing tiers) need different limits, and those limits need to be changeable without a redeploy. This means the rate limit rule itself (not just the counter) needs to be looked up dynamically per request: GET ratelimit-rule:{tenantId} from a fast config store (Redis, or an in-memory cache refreshed periodically from a config service) rather than hardcoded in application code. A common addition here is a quota (a longer-window cap — e.g. 1M requests/month) layered on top of the short-window rate limit (100 req/min) — the two serve different purposes: rate limiting protects against short bursts, quota enforcement protects the billing/business model, and a gateway often needs to check both independently.

Gateway-specific failure isolation

Since a gateway-level limiter sits in front of every request to every backend service, its own reliability matters more than a per-service limiter's would — a gateway rate limiter that adds significant latency or becomes unavailable affects the entire system, not just one endpoint. This is why the fail-open-vs-fail-closed decision (see Failure Scenarios below) is especially consequential at the gateway layer, and why the Redis dependency backing it needs its own health checks (Health Checks) feeding into the gateway's own resilience posture (a circuit breaker around the Redis call itself, defaulting to fail-open on timeout, is a common production pattern).

Bottlenecks

Redis is the single source of truth. Use Redis Cluster for horizontal scaling and Redis Sentinel for failover.

Failure Scenarios

If Redis is unavailable: fail open (allow all requests) or fail closed (deny all). Most APIs choose fail open to avoid an outage becoming a complete blackout.

Previous · Case study

Design a URL Shortener

Next · Case study

Design Twitter / X