Rate Limiting in Python with a Sliding Window

A beginner-friendly dataclass-based sliding window rate limiter that controls how many calls are allowed per time window.

Easy Python 3.9+ Aug 9, 2026 Reliability & rate limiting 12 views 0 copies

Python code

32 lines
Python 3.9+
import time
from dataclasses import dataclass


@dataclass
class RateLimiter:
    max_calls: int
    window_seconds: float = 1.0

    def __post_init__(self):
        self.calls = []
        self._start = time.monotonic()

    def _update(self, now):
        self.calls = [t for t in self.calls if now - t < self.window_seconds]

    def allow(self):
        now = time.monotonic()
        self._update(now)
        if len(self.calls) < self.max_calls:
            self.calls.append(now)
            return True
        return False


if __name__ == "__main__":
    limiter = RateLimiter(max_calls=3, window_seconds=0.5)

    for i in range(6):
        allowed = limiter.allow()
        print(f"Request {i+1}: {'allowed' if allowed else 'blocked'}")
        time.sleep(0.1)

Output

stdout
Request 1: allowed
Request 2: allowed
Request 3: allowed
Request 4: blocked
Request 5: allowed
Request 6: allowed

How it works

The RateLimiter uses a list of timestamps to track recent calls. Each allow() call records the current monotonic time and removes timestamps older than the window. If the number of stored timestamps is below the limit, the call is allowed and timestamp is added; otherwise it returns False to block. The sliding window updates lazily only when a new call arrives, which keeps overhead low. Using time.monotonic() avoids issues like system clock changes. This pattern is simple and easy to reason about, perfect for learning rate limiting fundamentals.

Common mistakes

  • Using `time.time()` instead of `time.monotonic()`, leading to wrong windows if the clock changes.
  • Forgetting to prune old timestamps, so the list grows indefinitely and blocks all future calls.
  • Not checking if `max_calls` is non‑negative, which causes unexpected behavior.
  • Hard‑coding sleep values in production instead of respecting the limiter's decisions.

Variations

  1. Use a `collections.deque` with `maxlen=max_calls` to keep only the most recent timestamps.
  2. Implement a fixed‑window limiter that resets a counter every `window_seconds`.

Real-world use cases

  • Limiting API calls from a background worker to stay within a provider's quota.
  • Throttling user‑triggered actions like sending SMS or password reset emails.
  • Protecting a microservice endpoint from burst traffic in a demo or small service.

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.