Rate Limiting with a Simple Python RateLimiter Class

A beginner-friendly Python rate limiter that tracks call timestamps and enforces a maximum number of calls within a rolling time window, with a helper to validate positive integers.

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

Python code

41 lines
Python 3.9+
import time

class RateLimiter:
    def __init__(self, max_calls, period_seconds):
        self.max_calls = max_calls
        self.period_seconds = period_seconds
        self.calls = []

    def is_allowed(self):
        now = time.time()
        while self.calls and now - self.calls[0] >= self.period_seconds:
            self.calls.pop(0)
        if len(self.calls) < self.max_calls:
            self.calls.append(now)
            return True
        return False

    def time_until_next_slot(self):
        if not self.calls or len(self.calls) < self.max_calls:
            return 0.0
        return self.period_seconds - (time.time() - self.calls[0])


def validate_positive_int(value):
    try:
        num = int(value)
    except (ValueError, TypeError):
        return False
    return num > 0


if __name__ == "__main__":
    limiter = RateLimiter(max_calls=3, period_seconds=5)

    for attempt in range(1, 6):
        allowed = limiter.is_allowed()
        print(f"Attempt {attempt}: allowed={allowed}")
        if not allowed:
            wait = limiter.time_until_next_slot()
            print(f"  -> Wait {wait:.2f} seconds before next try")
        time.sleep(0.5)

Output

stdout
Attempt 1: allowed=True
Attempt 2: allowed=True
Attempt 3: allowed=True
Attempt 4: allowed=False
  -> Wait 3.50 seconds before next try
Attempt 5: allowed=False
  -> Wait 3.00 seconds before next try

How it works

The RateLimiter stores timestamps of each allowed call in a list. When is_allowed is called, it first removes any timestamps older than the period (sliding window). If the number of stored calls is below the max, it appends the current time and returns True; otherwise it returns False. The time_until_next_slot method calculates the remaining wait time based on the oldest stored call, returning 0 if a slot is available. This simple design is easy to understand and works well for small-scale rate limiting, like throttling API calls in a script.

Common mistakes

  • Forgetting to handle the case when the calls list is empty in `time_until_next_slot`.
  • Using `time.sleep` in tests can slow down debugging; consider using a mock clock.
  • Popping from the front of a list (`pop(0)`) is O(n) but fine for small call counts; for high volume, use `collections.deque`.

Variations

  1. Use `collections.deque` for efficient FIFO operations.
  2. Use a simple counter-based approach (fixed window) for a less precise but simpler limiter.

Real-world use cases

  • Throttling outgoing HTTP requests to a third-party API to avoid hitting rate limits.
  • Limiting user actions in a CLI tool, such as max 5 login attempts per minute.
  • Controlling retry loops in a worker script to pace processing against a service's capacity.

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.