Mock Distributed Rate Limiter with Dict in Python

Simulates a distributed token-bucket rate limiter with a thread-safe dict, useful for testing before moving to Redis.

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

Python code

48 lines
Python 3.7+
import time
import threading
from collections import defaultdict


class DistributedRateLimiter:
    """
    A mock distributed rate limiter using a dict with thread-safe access.
    Implements a token bucket algorithm per user.
    """

    def __init__(self, rate_per_second=5, burst_capacity=10):
        self.rate_per_second = rate_per_second
        self.burst_capacity = burst_capacity
        self._buckets = defaultdict(lambda: {"tokens": burst_capacity, "last_refill": time.time()})
        self._lock = threading.Lock()

    def allow_request(self, user_id):
        with self._lock:
            bucket = self._buckets[user_id]
            now = time.time()
            elapsed = now - bucket["last_refill"]
            bucket["tokens"] = min(
                self.burst_capacity,
                bucket["tokens"] + elapsed * self.rate_per_second
            )
            bucket["last_refill"] = now

            if bucket["tokens"] >= 1:
                bucket["tokens"] -= 1
                return True
            return False


if __name__ == "__main__":
    limiter = DistributedRateLimiter(rate_per_second=2, burst_capacity=3)

    user = "user_42"

    # Simulate burst of requests
    for _ in range(5):
        print(f"Request allowed: {limiter.allow_request(user)}")

    # Wait for bucket to refill
    time.sleep(1)

    print(f"After 1s wait, request allowed: {limiter.allow_request(user)}")
    print(f"Request allowed: {limiter.allow_request(user)}")

Output

stdout
Request allowed: True
Request allowed: True
Request allowed: True
Request allowed: False
Request allowed: False
After 1s wait, request allowed: True
Request allowed: True

How it works

The token bucket algorithm maintains a token count per user, refilling at a fixed rate. The defaultdict creates a fresh bucket per user on first access, avoiding manual key setup. A threading.Lock serializes mutations, preventing race conditions in multi-threaded simulations. Waiting 1 second refills approximately 2 tokens, enough for the next two requests. This mock mirrors Redis-based designs where the bucket state lives in a shared store, replacing the dict with Lua scripts or Redis commands in production.

Common mistakes

  • Forgetting to use a lock and hitting race conditions in concurrent code
  • Not resetting `last_refill` after updating tokens, causing over-refill
  • Using float equality checks that can lead to precision errors
  • Hardcoding burst capacity instead of deriving from the rate

Variations

  1. Use Redis with `INCR` and `EXPIRE` for a fixed-window counter
  2. Implement sliding window log with `ZSET` for more precise control

Real-world use cases

  • Load-testing rate limiting logic locally without spinning up a Redis instance
  • Prototyping quota enforcement for API keys in a single-process service
  • Teaching token bucket concepts in interview prep or team onboarding

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.