How to Implement a Token Bucket Rate Limiter per Client IP in Python
Implements a simple sliding-window rate limiter using a dictionary of timestamp lists per client IP to limit requests per window.
Python code
28 linesfrom time import time
from collections import defaultdict
class RateLimiter:
def __init__(self, max_requests: int, window_seconds: int):
self.max_requests = max_requests
self.window_seconds = window_seconds
self.clients = defaultdict(list)
def allow(self, ip: str) -> bool:
now = time()
timestamps = self.clients[ip]
while timestamps and now - timestamps[0] >= self.window_seconds:
timestamps.pop(0)
if len(timestamps) < self.max_requests:
timestamps.append(now)
return True
return False
if __name__ == "__main__":
import time as t
limiter = RateLimiter(max_requests=3, window_seconds=5)
for i in range(5):
print(f"Request {i+1} from 192.168.1.10: {limiter.allow('192.168.1.10')}")
t.sleep(6)
print(f"After 6s, request from 192.168.1.10: {limiter.allow('192.168.1.10')}")
Output
Request 1 from 192.168.1.10: True
Request 2 from 192.168.1.10: True
Request 3 from 192.168.1.10: True
Request 4 from 192.168.1.10: False
Request 5 from 192.168.1.10: False
After 6s, request from 192.168.1.10: True
How it works
The RateLimiter uses a defaultdict(list) to store timestamps of requests per client IP, allowing O(1) access. On each call to allow, it first removes timestamps older than the window using a while loop, then checks if the remaining count is below the limit. If so, it appends the current time and returns True; otherwise, it returns False. This sliding-window approach is simple and effective for per-IP throttling in single-process applications. The time.time() function provides high-resolution Unix timestamps, ensuring accurate window boundaries.
Common mistakes
- Using a fixed counter without removing old timestamps, causing permanent blocking after limit is reached
- Not handling concurrent access with locks in a multi-threaded server, leading to race conditions
- Assuming the clock is monotonic, while `time.time()` can jump; prefer `time.monotonic()` for interval measures
Variations
- Use `time.monotonic()` instead of `time.time()` to avoid system clock adjustments
- Implement a fixed-window counter using a simple dict of (window_start, count) pairs
Real-world use cases
- Limiting login attempts per IP in a web service to prevent brute-force attacks.
- Throttling API calls per client IP to enforce usage quotas in a multi-tenant SaaS product.
- Rate-limiting requests from a specific IP in a reverse proxy or middleware layer to protect backend services from abuse.
Sponsored
More from Reliability & rate limiting
- At Least Once with Idempotent Consumer in Python medium
- Build a Rate Limiter Decorator in Python easy
- 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
Keep learning
Related tutorials and quizzes for this topic.