Rate limiting protects an API from being overwhelmed — whether by a genuine traffic spike, a buggy client stuck in a retry loop, or deliberate abuse — and the algorithm you choose has real behavioral trade-offs.
# Fixed window: simple, but allows bursting at window boundaries
# (e.g. 100 requests at 11:59:59, then another 100 at 12:00:00 = 200 in 1 second)
import time
requests_this_minute = {}
def fixed_window_allowed(client_id, limit=100):
window = int(time.time() // 60)
key = f"{client_id}:{window}"
requests_this_minute[key] = requests_this_minute.get(key, 0) + 1
return requests_this_minute[key] <= limit
# Token bucket: smoother, allows controlled bursting without the boundary problem
class TokenBucket:
def __init__(self, capacity, refill_rate):
self.capacity = capacity
self.tokens = capacity
self.refill_rate = refill_rate # tokens per second
self.last_refill = time.time()
def allow(self):
now = time.time()
self.tokens = min(self.capacity, self.tokens + (now - self.last_refill) * self.refill_rate)
self.last_refill = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
Communicating Limits to Clients
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 42 X-RateLimit-Reset: 1694208600 Retry-After: 30
Returning these headers on every response (not just when a limit is hit) lets well-behaved clients self-throttle proactively rather than discovering the limit only after being rejected — a meaningfully better API design than silently returning 429s with no advance warning.