Implementing Rate Limiting Patterns

1. Rate Limiting Pattern

GoalDetail
ProtectBackend from overload, abuse, runaway clients
Limit DimensionPer-IP, per-user, per-API-key, per-endpoint, global
Response When ExceededHTTP 429 + Retry-After
HeadersX-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset

2. Token Bucket Pattern

ElementDetail
Bucket CapacityMax burst (e.g., 100 tokens)
Refill RateTokens added/sec (e.g., 10/sec)
Request Cost1 token (or weighted)
BehaviorAllows bursts up to capacity

3. Leaky Bucket Pattern

ElementDetail
BucketQueue with fixed size
Leak RateFixed processing rate (smooths bursts)
OverflowReject excess
EffectConstant output rate; no burst tolerance

4. Sliding Window Pattern

VariantDetail
Sliding LogStore timestamp of each request; count last N seconds
Sliding CounterWeighted sum of current + previous fixed window
ProsFairer than fixed window; no edge bursts
ConsMore memory than fixed

5. Fixed Window Pattern

AspectDetail
MechanismCounter resets at start of each window (e.g., minute)
ProsSimplest; minimal state
ConsAllows 2× burst around window boundary

6. Adaptive Rate Limiting Pattern

AspectDetail
MechanismLimit adjusts based on system health (latency, errors, queue depth)
AlgorithmAIMD (additive increase, multiplicative decrease) — TCP-style
LibraryNetflix concurrency-limits, Envoy adaptive concurrency

7. Distributed Rate Limiting Pattern

ApproachDetail
Centralized CounterRedis INCR + EXPIRE; atomic Lua script
Token ServerCentral authority distributes tokens
Local + SyncEach node has local quota; sync periodically
Sticky RoutingHash key to one node; node owns counter

Example: Redis Token Bucket (Lua)

local key, capacity, refill, now, cost = KEYS[1], tonumber(ARGV[1]),
    tonumber(ARGV[2]), tonumber(ARGV[3]), tonumber(ARGV[4])
local tokens = tonumber(redis.call('hget', key, 't') or capacity)
local last = tonumber(redis.call('hget', key, 'ts') or now)
tokens = math.min(capacity, tokens + (now - last) * refill)
if tokens < cost then return 0 end
redis.call('hmset', key, 't', tokens - cost, 'ts', now)
redis.call('expire', key, 600)
return 1

8. Client-Based Rate Limiting

AspectDetail
WhereInside client SDK before sending
ProsAvoids wasted RTT to get rejected
ConsTrust required; client may bypass
Use WithServer-side as authoritative limit

9. API Quota Pattern

ElementDetail
Quota PeriodDay, month, billing cycle
Per PlanFree / Pro / Enterprise tiers
Soft vs HardSoft: warn at 80%; Hard: block at 100%
Carry-OverUsually no; resets each period

10. Rate Limit Response Pattern

Header / FieldPurpose
HTTP 429Too Many Requests status
Retry-AfterSeconds (or HTTP date) until next attempt allowed
X-RateLimit-LimitMax calls per window
X-RateLimit-RemainingCalls left
X-RateLimit-ResetUnix epoch when window resets
BodyProblem Details JSON with quota info