Build a Rate Limiter Decorator in Python
This code defines a reusable rate limiter decorator that caps function calls within a sliding time window using a deque and monotonic time.
Python code
32 linesimport time
from collections import deque
def rate_limiter(max_calls: int, period: float):
calls = deque()
def decorator(func):
def wrapper(*args, **kwargs):
now = time.monotonic()
while calls and now - calls[0] >= period:
calls.popleft()
if len(calls) >= max_calls:
raise RuntimeError(f"Rate limit exceeded: {max_calls} calls per {period}s")
calls.append(now)
return func(*args, **kwargs)
return wrapper
return decorator
@rate_limiter(max_calls=2, period=1.0)
def fetch_data(item_id):
return f"Data for item {item_id}"
if __name__ == "__main__":
for item in range(6):
try:
print(fetch_data(item))
except RuntimeError as e:
print(f"{item}: {e}")
time.sleep(0.3)
Output
Data for item 0
Data for item 1
2: Rate limit exceeded: 2 calls per 1.0s
Data for item 3
Data for item 4
5: Rate limit exceeded: 2 calls per 1.0s
How it works
The decorator keeps a deque of timestamps for recent calls. Each invocation uses time.monotonic() to get a reliable timestamp that isn't affected by system clock changes. Before adding a new call, it removes timestamps older than the period from the left of the deque, maintaining a sliding window. If the number of remaining calls equals or exceeds max_calls, it raises a RuntimeError; otherwise, it records the call and executes the function. This pattern is ideal for throttling external API requests or protecting internal resources from bursts.
Common mistakes
- Using `time.time()` instead of `time.monotonic()` — system clock changes can break the window.
- Forgetting to include the `@wraps` decorator, which loses the original function's metadata.
- Not handling the `RuntimeError` gracefully in production code, causing crashes.
- Sharing one limiter across multiple functions unintentionally, leading to over-throttling.
Variations
- Use `functools.lru_cache` or `threading.Lock` for thread-safe version in concurrent apps.
- Use `time.perf_counter` if you need higher precision for sub-millisecond windows.
Real-world use cases
- Limiting outbound requests to a third‑party API that has per‑second quotas.
- Throttling database write operations during high‑traffic spikes to prevent overload.
- Enforcing per‑user rate limits on a web service endpoint to prevent abuse.
Sponsored
More from Reliability & rate limiting
- At Least Once with Idempotent Consumer in Python medium
- Build a queue-based admission control system in Python easy
- Chaos Inject Random Failures in Python easy
- Circuit breaker failure threshold count in Python medium
- Exactly Once Processing Dedupe Mock in Python easy
- Fixed Window Counter Rate Limiting in Python easy
Keep learning
Related tutorials and quizzes for this topic.