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.
Python code
41 linesimport 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
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
- Use `collections.deque` for efficient FIFO operations.
- 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
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.