Rate Limiter is NOT Just a Design Pattern — It's a Survival Strategy
In software engineering, we often talk about caching, database indexing, and asynchronous queues to make APIs faster. But rate limiting serves a completely different purpose:
“A Rate Limiter doesn't make APIs faster — it makes systems survive.”
Without a rate limiter, a single rogue script, a DDoS attack, an aggressive web scraper, or a client-side infinite retry loop can consume all database connections and take down your entire infrastructure for every user.
A rate limiter controls the rate at which requests are sent by clients and processed by your server. If incoming requests exceed the configured threshold, excess calls are rejected (typically with an HTTP 429 Too Many Requests status code) or delayed.
1. Token Bucket Algorithm
The Token Bucket is a container with a fixed capacity. Tokens are added into the bucket at a constant refill rate (e.g. 5 tokens per second). When a request arrives, the system checks if a token is available:
- If tokens are available: 1 token is consumed, and the request is processed.
- If bucket is empty: The request is dropped or rejected immediately.
- If bucket is full: Newly generated tokens overflow and are discarded.
- • Easy to implement in Redis with simple atomic commands.
- • Highly memory efficient: only tracks token count and last timestamp.
- • Allows short traffic bursts as long as tokens are in the bucket.
- • Sudden bursts can still stress downstream databases.
- • Requires careful tuning of bucket capacity and refill rate.
- • Does not smooth traffic evenly over time.
2. Leaking Bucket Algorithm
The Leaking Bucket algorithm is similar to the Token Bucket, but it handles traffic through a FIFO (First In, First Out) queue. Imagine a bucket with a small hole at the bottom:
Incoming requests enter the top of the bucket. If the queue has room, the request is queued. If the queue is full, incoming requests overflow and are dropped immediately. Meanwhile, requests leak out the bottom and are processed by the server at a strictly constant, fixed rate.
- • Processes requests at a completely smooth, constant rate.
- • Eliminates sudden traffic spikes from reaching your database.
- • Memory efficient since queue capacity is strictly bounded.
- • Does not allow traffic bursts even if backend is idle.
- • Older slow requests in queue can block newer critical requests.
- • Dropped requests can frustrate legitimate users during spikes.
3. Fixed Window Counter Algorithm
The Fixed Window Counter divides timeline into fixed intervals (e.g., 1-minute blocks like 10:41:00 to 10:42:00). Each window has an atomic counter. Every request increments the counter. If the counter exceeds the allowed threshold (e.g. 10 requests), subsequent requests are rejected. When the clock enters the next minute, the counter resets back to zero.
Suppose your limit is 10 requests per minute. If a user sends 10 requests at 10:41:59and another 10 requests at 10:42:01, the system allows 20 requests within 2 seconds! This 2x burst at window boundaries can crash backend services.
- • Simplest possible algorithm to implement.
- • Tiny memory footprint (single Redis integer key with TTL).
- • Blazing fast performance.
- • Allows 2x traffic bursts at window boundaries.
- • Inaccurate rate enforcement.
- • Easily abused by clients timing their request batches.
4. Sliding Window Counter Algorithm
The Sliding Window Counter solves the boundary spike problem of the Fixed Window without the heavy memory penalty of logging every timestamp.
It combines the request count from the current window with a weighted percentage from the previous window:
Estimated Requests = (Current Window Requests) +
(Previous Window Requests * (1 - Current Window Time Elapsed %))For example, if the previous minute had 10 requests and 30% of the current minute has passed with 3 requests, the estimated rate is: 3 + (10 * 0.70) = 10 requests. If the limit is 10, the next request is rejected.
- • Dramatically smooths boundary spikes.
- • Very low memory usage: only tracks 2 numbers per user.
- • Fair and realistic for modern production workloads.
- • Slightly more complex math than fixed windows.
- • Still an approximation (assumes evenly spread requests in previous window).
5. Sliding Window Log Algorithm
The Sliding Window Log algorithm maintains an exact timestamp for every incoming request, usually in a Redis Sorted Set (ZSET).
- When a request arrives, remove all timestamps older than
(Current Time - Window Size). - Count how many timestamps remain in the set.
- If the count is within the limit, add the current timestamp and accept the request.
- If the count exceeds the limit, reject the request.
-- Redis Lua script for precise Sliding Window Log:
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local clearBefore = now - window
-- 1. Remove expired timestamps
redis.call('ZREMRANGEBYSCORE', key, 0, clearBefore)
-- 2. Count current requests
local currentRequests = redis.call('ZCARD', key)
if currentRequests < limit then
-- 3. Add timestamp and accept
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, math.ceil(window / 1000))
return 1
else
return 0 -- Rejected!
end- • 100% mathematically accurate rate limiting.
- • Completely eliminates boundary spikes.
- • Strict and fair enforcement.
- • High memory consumption (stores every timestamp).
- • Heavy Redis memory footprint under high traffic.
- • More expensive to maintain at scale.
Which Rate Limiter Should You Choose?
There is no single “best” algorithm. The right choice depends on your specific traffic patterns and backend tolerance:
| Algorithm | Burst Handling | Memory Cost | Accuracy | Best Fit |
|---|---|---|---|---|
| Token Bucket | Allows Bursts | Very Low (O(1)) | Good | User-facing REST APIs (Stripe, GitHub) |
| Leaky Bucket | No Bursts (Smooth) | Low (Fixed Queue) | High | Message queues, Webhook delivery |
| Fixed Window | Boundary 2x Burst | Lowest (1 Integer) | Low | Simple internal tools, daily limits |
| Sliding Counter | Smooths Boundaries | Very Low (2 Ints) | High | Production microservices (Best Balance) |
| Sliding Log | Strictly Controlled | High (O(N) entries) | 100% Exact | Auth, OTPs, Payment checkout |
The Engineering Mindset
Rate limiting is not about punishing or blocking users. It is about protecting your infrastructure, preventing noisy neighbors, and ensuring fairness for every single person using your platform.
Always choose the algorithm that matches your traffic pattern — not just the easiest one to copy-paste.