API Rate Limiting Algorithms in Production: Token Bucket, Sliding Window & Redis Lua in 2026

A comprehensive engineering guide to API rate limiting: Token Bucket, Leaky Bucket, Sliding Window Log/Counter, and atomic multi-instance Redis Lua scripts.
API Rate Limiting Algorithms in Production: Token Bucket, Sliding Window & Redis Lua in 2026
In distributed systems, an API without robust rate limiting is an open invitation for disaster:
- A single buggy client script in an infinite
while(true)loop can exhaust your backend thread pools and crash production databases. - Credential-stuffing bots can attempt 50,000 password combinations per minute against your login endpoint.
- Malicious scrapers can crawl your entire proprietary product catalog, draining server bandwidth and inflating cloud compute bills.
However, implementing a naive rate limiter using standard GET and SET operations in Redis or memory introduces critical race conditions: two concurrent HTTP requests can read the counter simultaneously, both pass the check, and both decrement the counter, allowing attackers to exceed rate limits by 200% to 500%.
In 2026, enterprise rate limiting requires understanding the algorithmic trade-offs between Token Bucket, Leaky Bucket, Fixed Window, and Sliding Window Counter, implemented via Atomic Redis Lua Scripts with standard HTTP header contracts.
In this deep architectural guide, we break down how to design, benchmark, and deploy production rate limiters based on high-throughput security architectures engineered at MojoStudio.
1. The Rate Limiting Algorithm Master Comparison
+-----------------------------------------------------------------------------------------+
| Rate Limiting Algorithm Comparison Matrix |
+-----------------------------------------------------------------------------------------+
TOKEN BUCKET (The Universal Standard)
- Refills tokens at a constant rate (e.g. 10 tokens/sec) up to bucket capacity (100).
- Burst Handling: Allows controlled bursts up to bucket capacity.
- Best for: General REST & GraphQL APIs, bursty user web traffic.
LEAKY BUCKET (Traffic Shaping / Smoothing)
- Requests enter a FIFO queue; processed at a strictly constant smooth rate.
- Burst Handling: Buffers bursts, drops when buffer overflows.
- Best for: Third-party webhook dispatch, downstream message processing.
FIXED WINDOW COUNTER (Naive Time Buckets)
- Resets counter at exact clock intervals (e.g., 12:00:00, 12:01:00).
- Flaw: Vulnerable to "Boundary Bursts" (2x limit across window edges).
- Best for: Simple login attempts, low-traffic endpoints.
SLIDING WINDOW COUNTER (Weighted Balance) [THE 2026 PRODUCTION GOLD STANDARD]
- Blends the previous window's count with the current window's count based on percentage elapsed.
- Memory: Ultra-low (2 integers).
- Best for: High-scale public APIs with strict fairness and zero boundary bursts.| Algorithm | Burst Handling | Boundary Spike Protection | Memory Footprint | Accuracy | Best Use Case |
|---|---|---|---|---|---|
| Token Bucket | Controlled Bursts | Immune | Low (Tokens + Timestamp) | High | General Public APIs |
| Leaky Bucket | None (Smooths) | Immune | Low (Queue depth) | High | Egress Webhooks |
| Fixed Window | High (2x Spike) | Vulnerable | Lowest (1 integer) | Low | Login Brute-Force |
| Sliding Window Log | No Bursts | Immune | High (Sorted Set of Timestamps) | 100% Exact | Financial Micro-transactions |
| Sliding Window Counter | Minimal | Immune | Lowest (2 counters) | ~99% Exact | High-Throughput Global APIs |
2. Deep Dive: The Sliding Window Boundary Spike Flaw
To understand why Fixed Window rate limiting fails, consider a limit of 100 requests per minute:
[11:59:50 - 11:59:59] ---> Attacker sends 100 requests (Allowed by Window 1)
[12:00:00 - 12:00:10] ---> Attacker sends 100 requests (Allowed by Window 2)
RESULT:
Attacker sent 200 requests in a 20-second span!
The server received 2x the allowed rate limit, potentially crashing downstream databases.The Sliding Window Counter Solution: When a request arrives at 12:00:15 (25% into the current 1-minute window), the algorithm calculates:
\text{Estimated Requests} = (\text{Previous Window Count} \times 0.75) + \text{Current Window Count}If the estimated total exceeds 100, the request is immediately rejected with a 429 Too Many Requests status.
3. Why Redis Lua Scripting is Mandatory for Concurrency
In a distributed environment with 20 load-balanced backend pods, executing rate checks in application code causes race conditions:
[Pod A reads Redis: count = 99]
[Pod B reads Redis: count = 99]
[Pod A increments: count = 100 (Allowed)]
[Pod B increments: count = 101 (Allowed! Breach!)]Redis executes Lua scripts atomically in a single event loop tick. No other Redis command can execute between reading the counter, evaluating the time window, and updating the key:
+-----------------------------------------------------------------------------------------+
| Atomic Redis Lua Sliding Window Execution |
+-----------------------------------------------------------------------------------------+
[Client Request arrives at API Gateway]
|
v (Single Atomic Redis Round-Trip via EVALSHA)
+-----------------------------------------------------------------+
| Redis Lua Engine: |
| 1. Expire outdated timestamps: ZREMRANGEBYSCORE key 0 (now-60s) |
| 2. Count current active requests: ZCARD key |
| 3. If count < limit: ZADD key now (now + uuid) -> Return ALLOWED|
| 4. If count >= limit: Return BLOCKED + Retry-After seconds |
+-----------------------------------------------------------------+4. Production Implementation: Sliding Window Rate Limiter in TypeScript & Lua
import { createClient } from "redis";
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
// Atomic Sliding Window Rate Limiter Lua Script
const slidingWindowLuaScript = `
local key = KEYS[1]
local now = tonumber(ARGV[1])
local windowSizeMs = tonumber(ARGV[2])
local maxLimit = tonumber(ARGV[3])
local clearBefore = now - windowSizeMs
-- 1. Remove expired timestamps
redis.call('ZREMRANGEBYSCORE', key, 0, clearBefore)
-- 2. Count current elements in window
local currentCount = redis.call('ZCARD', key)
if currentCount < maxLimit then
-- Add unique member (timestamp + random float)
redis.call('ZADD', key, now, now .. '-' .. math.random())
redis.call('PEXPIRE', key, windowSizeMs)
return {1, maxLimit - currentCount - 1, 0} -- {isAllowed, remaining, retryAfterSeconds}
else
local oldest = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES')
local retryAfter = math.ceil((tonumber(oldest[2]) + windowSizeMs - now) / 1000)
return {0, 0, retryAfter}
end
`;
export async function checkRateLimit(identifier: string, limit: number = 60, windowSeconds: number = 60) {
const key = `ratelimit:${identifier}`;
const now = Date.now();
const windowMs = windowSeconds * 1000;
const [isAllowed, remaining, retryAfter] = (await redis.eval(slidingWindowLuaScript, {
keys: [key],
arguments: [now.toString(), windowMs.toString(), limit.toString()],
})) as [number, number, number];
return {
allowed: isAllowed === 1,
remaining,
retryAfterSeconds: retryAfter,
};
}5. Modern HTTP Header Standards (RFC 6585)
When rate limiting clients, your API must communicate limits using standard HTTP headers so SDKs and mobile apps can automatically back off:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 24
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1714829420
{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Please retry after 24 seconds.",
"retry_after_seconds": 24
}Key Headers:
Retry-After: The number of seconds the client MUST sleep before attempting another request.X-RateLimit-Limit: The maximum number of requests allowed in the active time window.X-RateLimit-Remaining: The number of remaining requests allowed before throttling kicks in.
Conclusion: Engineering Bulletproof API Defenses
API rate limiting is the frontline shield protecting backend databases, cloud compute resources, and downstream microservices from traffic surges, malicious scrapers, and denial-of-service attacks.
By replacing naive fixed windows with Sliding Window Counter algorithms, executing checks atomically in Redis via Lua scripts, and returning clear RFC-compliant 429 Retry-After headers, engineering teams can build resilient APIs that scale under immense load with 100% fairness and zero race conditions.
At MojoStudio, our backend engineering team designs high-throughput API gateways, DDoS protection layers, and distributed rate limiting architectures. Contact our team to audit and secure your API infrastructure today.
Frequently Asked Questions
1. What is the difference between Token Bucket and Sliding Window rate limiting?
Token Bucket allows controlled traffic bursts up to a fixed bucket capacity while refilling tokens at a constant rate. Sliding Window calculates a rolling continuous average across time windows, preventing boundary burst spikes with higher mathematical precision.
2. Why is a Redis Lua script necessary for rate limiting?
In a multi-server distributed environment, separate GET and INCR commands create race conditions where concurrent requests pass limit checks simultaneously. A Redis Lua script executes atomically in a single pass, guaranteeing exact counter increments.
3. What HTTP status code should be returned when a rate limit is exceeded?
APIs must return HTTP 429 Too Many Requests accompanied by a Retry-After: <seconds> header indicating how long the client must wait before retrying.
4. What is the "Boundary Spike" problem in Fixed Window rate limiters?
In a fixed window (e.g., 100 requests per minute resetting at 12:00:00), an attacker can send 100 requests at 11:59:59 and 100 requests at 12:00:01, delivering 200 requests within 2 seconds without triggering the rate limit.
5. How should rate limit keys be structured for different user tiers?
Rate limits should be keyed dynamically: by IP address for unauthenticated public endpoints (ratelimit:ip:192.168.1.1), and by User ID or Organization ID for authenticated enterprise tiers (ratelimit:org:acme_101).
6. What is the Leaky Bucket algorithm and where is it used?
Leaky Bucket processes incoming requests at a strictly constant, smoothed output rate regardless of incoming burst volume, making it ideal for traffic shaping and outgoing third-party webhook delivery.
7. How does rate limiting protect against DDoS and brute-force attacks?
By restricting the number of allowed requests per IP address or user account per minute, rate limiters prevent automated bots from executing millions of credential-stuffing login attempts or exhausting server CPU threads.
8. What is the performance latency overhead of a Redis rate limit check?
An atomic Redis Lua sliding window script typically executes in under 1 to 2 milliseconds, adding negligible latency to incoming API requests.
9. How do client SDKs handle 429 rate limit errors?
Client SDKs inspect the Retry-After header and implement exponential backoff with randomized jitter before retrying the failed request, preventing thundering herd spikes when limits reset.
10. How can MojoStudio help us implement rate limiting?
MojoStudio engineers custom Redis rate limiters, API gateway middlewares, DDoS mitigation pipelines, and multi-tier usage metering systems. Explore our Backend Engineering Services to learn more.
Frequently Asked Questions
Token Bucket allows controlled traffic bursts up to a fixed bucket capacity while refilling tokens at a constant rate. Sliding Window calculates a rolling continuous average across time windows, preventing boundary burst spikes with higher mathematical precision.