Rate Limiter — LLD Mastery

Low-level design

Designing a rate limiter

A gatekeeper that decides, in under a millisecond, whether a request gets to proceed or gets turned away — protecting shared resources from being overwhelmed by any single client, on purpose or by accident.

Strategy pattern  ·  middleware  ·  distributed systems  ·  concurrency

Requests arrive, the gate decides

allowed 0 allowed · 0 blocked blocked
STEP 1

Understanding & Motivation

Before designing anything, articulate *why* a system needs rate limiting. Interviewers are listening for whether you connect the mechanism to real failure modes, not just recite algorithm names.

DoS / abuse prevention

Blocks a single bad actor (or buggy retry loop) from monopolizing capacity meant for everyone else.

Resource starvation

Protects finite resources — DB connections, thread pools, third-party quotas — from being exhausted by traffic spikes.

Cost optimization

Every request often has a real dollar cost (compute, egress, downstream API calls) — limiting caps the bill.

Cascading failure mitigation

Stops overload in one service from rippling upstream and taking down every dependent service with it.

Illustrative real-world limits (rounded, publicly documented order-of-magnitude)

Service Typical Limit Why this shape
GitHub REST API ~5,000 req/hour (authenticated) Long window, high ceiling — optimized for scripts & CI, not bursts.
Stripe API ~100 req/sec (live mode) Short window — protects a payments-critical path from spikes.
Social APIs (e.g. X/Twitter) Tiered, per 15-min window Per-endpoint tiers so expensive reads are throttled harder than cheap ones.
STEP 2

Requirements Gathering

Split requirements into functional (what it does) and non-functional (how well it does it) — interviewers explicitly score this separation.

Functional Requirements

  • Allow or block a request based on a configurable threshold.
  • Support scoping by client ID, IP address, API token, or user tier.
  • Different limits per route/endpoint (e.g. cheap read vs. expensive write).
  • Give real-time feedback via standard response headers.
  • Return 429 Too Many Requests with a Retry-After hint.

Non-Functional Requirements

  • Low latency overhead — check should add <2ms to the request path.
  • Memory-efficient — ideally O(1) state per client, not O(requests).
  • Horizontally scalable — correct even across many stateless API instances.
  • High availability — the limiter must never become the outage.
  • Graceful degradation — decide fail-open vs. fail-closed if the store is down.

Standard response contract

Success — headers on every response

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1755392400

Denied — limit exceeded

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Remaining: 0

{"error":"rate_limit_exceeded"}
STEP 3

Design Patterns Applied

A rate limiter is a great vehicle for demonstrating pattern fluency — naming the pattern is worth less than explaining why it fits here.

Strategy Pattern

A common RateLimiter interface with interchangeable algorithm implementations (Token Bucket, Sliding Window, ...) selected per route or client tier at runtime.

Decorator / Middleware Pattern

Rate limiting wraps the request handler as a composable middleware layer — stacked with auth and logging, without the handler ever knowing it exists.

Singleton / Factory Pattern

A RateLimiterFactory builds and caches limiter instances per config; the underlying store connection (e.g. a Redis pool) is typically a process-wide singleton.

Shared State Synchronization

Once you scale horizontally, bucket/window state must live in a shared store (Redis) or be reconciled across instances — this is where most interviews pivot to distributed design.