python 96 lines · 3 tabs

Sliding-Window Rate Limiting in Flask With a Custom Decorator and In-Memory Buckets

Shared by codesnips Jul 2026
3 tabs
import time
import threading
from collections import deque, defaultdict


class SlidingWindowLimiter:
    def __init__(self, limit, window):
        self.limit = limit
        self.window = float(window)
        self._buckets = defaultdict(deque)
        self._lock = threading.Lock()

    def hit(self, key):
        now = time.monotonic()
        cutoff = now - self.window
        with self._lock:
            events = self._buckets[key]
            while events and events[0] <= cutoff:
                events.popleft()

            if len(events) >= self.limit:
                reset = events[0] + self.window
                return False, 0, reset

            events.append(now)
            remaining = self.limit - len(events)
            reset = events[0] + self.window
            return True, remaining, reset
3 files · python Explain with highlit

This snippet builds a request rate limiter for Flask using a hand-rolled sliding-window algorithm stored in process memory, with no external dependencies like Redis. It is useful for single-process deployments, internal tools, or prototypes where a full distributed limiter would be overkill, and it doubles as a clear illustration of how sliding windows and token accounting actually work.

The rate_limit.py module defines SlidingWindowLimiter, which keeps a deque of request timestamps per client key. On each hit, timestamps older than the window are popped from the left, so the deque only ever holds events inside the current window; the remaining length is the request count. This gives a true sliding window rather than the cheaper fixed-window approach, which suffers from burst doubling at the boundary between two windows. A threading.Lock guards each mutation because Flask's default WSGI servers can serve requests on multiple threads, and an unprotected deque would corrupt under concurrent hit calls. The hit method returns both an allow/deny decision and metadata (remaining and reset) so callers can populate standard headers.

The decorators.py file wraps the limiter in a rate_limit decorator factory. It uses functools.wraps to preserve the view's identity, derives a client key from an override function or the remote IP, and on rejection raises a 429 via abort with Retry-After. The interesting part is _apply_headers: it attaches X-RateLimit-* headers to every response, including allowed ones, so clients can self-throttle before hitting the wall. A per-endpoint SlidingWindowLimiter is created once at decoration time, keeping state isolated between routes.

In app.py, the decorator is applied to two routes with different budgets, showing how limits are tuned per endpoint. A shared after_request hook is unnecessary because the decorator manages its own headers. The main trade-off is honesty: because state lives in memory, limits reset on restart and are not shared across workers, so a multi-process gunicorn deployment would let each worker grant the full quota. For that case a shared store is required, but the interface here is designed so swapping the backend is straightforward. The pitfall to watch is unbounded growth of the per-key dictionary; a production version would evict idle keys on a timer.


Related snips

Share this code

Here's the card — post it anywhere.

Sliding-Window Rate Limiting in Flask With a Custom Decorator and In-Memory Buckets — share card
Link copied