Part 1 · Concepts · Built for backends
Token bucket rate limiter
Allows bursts up to a limit, then a steady rate. The standard way to rate limit an API.
How it works
The bucket holds up to capacity tokens and refills at a fixed rate. Each request takes a token; an empty bucket means reject (HTTP 429). Refill lazily: on each request, add elapsed time × rate, capped at capacity. No timers needed. Passing the clock in makes it testable.
| tryTake | O(1) |
| memory | O(1) per client key |
Reach for it when
- 'Design a rate limiter'
- Protect an API or a third-party quota
- Smooth bursts of jobs
Implementation (JavaScript, tested)
// Allows bursts up to `capacity`, refills at `ratePerSec`. `now` is injectable so it's testable.
class TokenBucket {
capacity;
ratePerSec;
now;
tokens;
last;
constructor(capacity, ratePerSec, now = Date.now) {
this.capacity = capacity;
this.ratePerSec = ratePerSec;
this.now = now;
this.tokens = capacity;
this.last = now();
}
tryTake(cost = 1) {
const t = this.now();
this.tokens = Math.min(this.capacity, this.tokens + ((t - this.last) / 1000) * this.ratePerSec);
this.last = t;
if (this.tokens < cost) return false; // caller returns HTTP 429
this.tokens -= cost;
return true;
}
}Traps
- Across many servers, an in-memory bucket isn't shared. Use Redis with a Lua script (atomic read-modify-write) or the gateway.
- Fixed window counters allow a 2x burst at window edges. Sliding window or token bucket fix that.
- Use a monotonic clock in production; wall clocks can jump.
In real systems: Kong's rate limiting plugin, AWS API Gateway throttling (token bucket), nginx limit_req (leaky bucket, a close cousin).
Say out loud: Lazy refill means no background timer, O(1) per request. For many servers I'd keep the bucket in Redis and update it atomically with a Lua script.