Rate Limiting Algorithms Compared
Introduction
Rate limiting is a core control for API reliability and abuse prevention. The algorithm you choose determines fairness, burst tolerance, and operational complexity. This guide compares the most common algorithms and when to use them.
Fixed Window Counter
A fixed window counter increments requests inside a fixed interval.
- Simple and memory efficient.
- Allows bursty traffic at window boundaries.
- Best for coarse limits where precision is not critical.
Sliding Window Log
The sliding window log stores timestamps for every request.
- Precise enforcement without boundary spikes.
- High memory usage under heavy load.
- Best for smaller scale or low request volumes.
Sliding Window Counter
A sliding window counter approximates the log by using two buckets.
- Lower memory than full logs.
- Smooths boundary spikes with weighted buckets.
- Suitable for distributed systems with shared counters.
Token Bucket
Token bucket allows bursts while enforcing a steady refill rate.
- Great for APIs that allow short bursts.
- Enforces average rate over time.
- Commonly used for public APIs and gateway limits.
Leaky Bucket
Leaky bucket enforces a strict outflow rate.
- Prevents bursts entirely.
- Useful when downstream systems cannot handle spikes.
- Often implemented as a queue with fixed drain rate.
Trade-off Matrix
| Algorithm | Burst Support | Precision | Memory Cost | Typical Use |
|---|---|---|---|---|
| Fixed Window | High at boundaries | Low | Low | Simple quotas |
| Sliding Log | Low | High | High | Small systems |
| Sliding Counter | Medium | Medium | Medium | Distributed limits |
| Token Bucket | High | Medium | Low | Public APIs |
| Leaky Bucket | Low | Medium | Low | Backpressure control |
JavaScript Example: Token Bucket
The following JavaScript example implements a token bucket for a single key.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class TokenBucket {
constructor(ratePerSecond, capacity) {
this.ratePerSecond = ratePerSecond;
this.capacity = capacity;
this.tokens = capacity;
this.lastRefill = Date.now();
}
allowRequest() {
const now = Date.now();
const elapsed = (now - this.lastRefill) / 1000;
const refill = elapsed * this.ratePerSecond;
this.tokens = Math.min(this.capacity, this.tokens + refill);
this.lastRefill = now;
if (this.tokens >= 1) {
this.tokens -= 1;
return true;
}
return false;
}
}
Conclusion
Pick the algorithm that matches your burst tolerance and enforcement precision. Many high-scale systems mix token bucket at the edge with sliding counters at the service layer to balance fairness and performance.