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.

Medium Python 3.9+ Aug 9, 2026 Reliability & rate limiting 13 views 0 copies

Python code

28 lines
Python 3.9+
from 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

stdout
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

  1. Use `time.monotonic()` instead of `time.time()` to avoid system clock adjustments
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Reliability & rate limiting

Related tutorials and quizzes for this topic.