Rate limiting sounds simple: count requests, block when over the limit.
But the algorithm you choose changes how your API behaves under load.
Here's what's actually happening.
Fixed Window
The simplest algorithm. Count requests in a fixed time window (e.g. 60
seconds). When the counter hits the limit, block until the window resets.
Window: [0s -------- 30s -------- 60s]
Requests: 1 2 3 4 5 ... 30 = DENY
Window resets
Requests: 1 2 3 4 5 ... 30 = DENY
Problem: boundary spikes. If the limit is 30/minute and a client sends
30 requests at 59s and 30 at 61s, they got 60 requests in 2 seconds.
Use when: login protection, payment endpoints, strict quotas.
Token Bucket
Each client gets a bucket of tokens. Tokens refill at a constant rate.
Each request costs one token. If the bucket is empty, the request is denied.
Bucket: [========] 8/10 tokens
Request costs 1 token → [=======] 7/10
Refill 1 token/sec → [========] 8/10 (after 1s)
Key property: allows bursts. If you haven't made requests for a while,
your bucket fills up and you can burst above the average rate.
Use when: general API protection, where short bursts are acceptable.
Sliding Window
Like Fixed Window, but the window slides continuously. Instead of counting
in a fixed block, it counts requests in the last N seconds.
Window: [------======------]
now-60s now
Requests: 1 2 3 4 5 6 = 6 in last 60s
Key property: no boundary spikes. Smooth rate limiting.
Use when: fair API usage, where you want consistent behavior.
GCRA (Generic Cell Rate Algorithm)
Used in telecom networks. Instead of counting requests, it tracks the
"theoretical arrival time" of the next allowed request.
Key property: very smooth, memory-efficient, no bucket to overflow.
Use when: high-throughput APIs, telecom-grade traffic shaping.
Leaky Bucket
Requests enter a bucket that leaks at a constant rate. If the bucket
overflows, requests are denied. Output is constant-rate regardless of
input spikes.
Input: [======] (burst)
Bucket: [====] (buffered)
Output: [=] (constant rate)
Use when: background jobs, queues, APIs that need constant processing rate.
Side-by-side
| Token Bucket | Fixed Window | Sliding Window | GCRA | Leaky Bucket | |
|---|---|---|---|---|---|
| Bursts allowed | Yes | No | No | No | No |
| Boundary spikes | No | Yes | No | No | No |
| Memory per key | Low | Very low | Medium | Very low | Low |
| Smoothness | Medium | Low | High | Very high | High |
| Complexity | Medium | Low | Medium | Medium | Medium |
The choice matters
Most rate limiters default to Fixed Window because it's simple. But Fixed
Window has the boundary spike problem: a clever client can get 2x the limit
by timing requests around the window boundary.
Token Bucket is usually the better default: it allows natural bursts while
still enforcing the average rate. If you need strict quotas (no bursts at
all), use Fixed Window explicitly.
In Python
from drogue.core.rules.rule import AlgorithmType
# Burst-friendly (default)
@limiter.limit("30/minute")
# Strict per-window quota
@limiter.limit("30/minute", algorithm=AlgorithmType.FIXED_WINDOW)
# Smooth, no boundary spikes
@limiter.limit("30/minute", algorithm=AlgorithmType.SLIDING_WINDOW)
All five algorithms are available. Pick based on your traffic pattern.
Top comments (0)