pythonintermediate45 minPython 3

Sliding Window Token Bucket Rate Limiter

Implement a stateful token bucket rate limiter in Python that throttles high-volume API requests and enforces tenant quotas.

The incident

Company
VelocePay Networks
Engineer
Divya Krishnan, Backend Infrastructure Engineer
Trigger
Unthrottled webhook callbacks during merchant sale spikes caused gateway CPU saturation and cascaded into 504 gateway timeouts.
Stakes
Over 85,000 incoming webhooks flooded API gateways per minute, causing 18 lakh rupees in delayed merchant notifications and infrastructure autoscaling charges.

During a nationwide flash sale campaign hosted by regional retail partners, VelocePay Networks experienced catastrophic traffic surges across its public merchant webhook ingestion endpoints. Over eighty-five thousand webhook callbacks per minute slammed the API gateway cluster without warning. Because no rate limiting controls had been provisioned on the ingestion microservice, background worker queues backed up rapidly, exhausting CPU thread pools and causing database connection timeouts across downstream billing services, resulting in severe processing delays, memory thrashing, and thread pool starvation across container nodes.

The cascading infrastructure degradation triggered widespread HTTP 504 gateway timeouts for legitimate merchant checkout confirmation webhooks across multiple regional enterprise hubs. Over eighteen lakh rupees in automated merchant payouts and transaction confirmations were delayed by several hours. Partner merchants flooded the engineering support desk with high-priority tickets, while the cloud infrastructure autoscaler spun up dozens of emergency compute nodes, creating substantial unbudgeted cloud infrastructure expenses that threatened monthly infrastructure operating budgets and breached executive cloud expenditure limits.

Investigation into the API gateway layer revealed that incoming requests were accepted unconditionally as long as HTTP headers were valid. Aggressive automated scraping bots and high-volume merchant ERP integrations were submitting bursts of hundreds of concurrent requests within single milliseconds, starving smaller merchants of gateway bandwidth and crashing container pods under memory pressure. The gateway had no mechanism to enforce fair-share multi-tenant bandwidth allocations or protect backend microservices from sudden traffic spikes.

The lead systems architect tasked Divya Krishnan, a backend infrastructure engineer, with implementing an in-memory token bucket rate limiter in Python. The rate limiting algorithm needed to enforce strict per-client burst capacities while continuously replenishing request tokens at a smooth, predictable rate. Crucially, the solution needed to execute as a few arithmetic operations per request without requiring external distributed cache clusters for local node protection during extreme operational load spikes across active traffic windows and sudden flash sale events.

Divya selected the token bucket algorithm due to its optimal balance between burst tolerance and sustained throughput control. A bucket holds up to a maximum capacity of tokens and refills at a constant rate per second. Each incoming request consumes one token. If the bucket is empty, the request is immediately rejected with HTTP 429 Too Many Requests, protecting downstream microservices from queue saturation.

When tested against production traffic replay logs, Divya token bucket rate limiter smoothly absorbed legitimate merchant traffic bursts while clamping abusive automated retry storms, reducing peak gateway CPU utilization by sixty-two percent. The rate limiter configuration parameters, client quota policies, and rejection telemetry are reviewed weekly by the Platform Security Team and Infrastructure Operations Lead to ensure consistent service availability across regional partners and prevent thundering herd outages during peak merchant clearing windows.

What you will learn

  • Implement the mathematical token bucket rate limiting algorithm in Python
  • Model continuous token replenishment using fractional elapsed time
  • Enforce tenant-level isolation using client key mapping
  • Reject burst over-allocations with clean boolean rate-limiting responses

What you need first

Token Bucket AlgorithmSliding window and buffers

A token bucket allows bursts up to a maximum capacity while replenishing tokens continuously at a constant fill rate per second.

PythonMini example
tokens = min(10, 5 + 2.5 * 2); print(tokens)
10.0
Watch out

Failing to cap replenished tokens at the bucket capacity allows infinite tokens to accumulate during idle periods.

Elapsed Time ReplenishmentDates and timestamps in Python

Rather than running background worker threads to refill buckets, lazy replenishment calculates elapsed time between requests on-demand.

PythonMini example
elapsed = 1.5; rate = 2.0; print(elapsed * rate)
3.0
Watch out

Spawning separate background threads per client to add tokens consumes excessive OS threads and causes concurrency race conditions.

Tenant-Isolated StatePython hash maps

Mapping client IP addresses or API keys to individual bucket instances ensures that noisy neighbors cannot exhaust other tenants' quotas.

PythonMini example
d = {}; d.setdefault('client_1', 10); print(d['client_1'])
10
Watch out

Using a single global bucket across all clients allows a single abusive script to starve all legitimate users of API bandwidth.

Architecture and data flow

Problem scenario diagram for py-proj-token-bucket-rate-limiter
Problem scenario diagram for py-proj-token-bucket-rate-limiter. Figure 1: Operational problem and business context
Data flow diagram for py-proj-token-bucket-rate-limiter
Data flow diagram for py-proj-token-bucket-rate-limiter. Figure 2: Pipeline execution and data transformation flow
Result shape diagram for py-proj-token-bucket-rate-limiter
Result shape diagram for py-proj-token-bucket-rate-limiter. Figure 3: Output verification and method results

The data

Client API request stream with client ID, request timestamp, and endpoint URL.

api_requests: Incoming API webhook call stream

client_idtimestampendpoint
client_A14:32:01.100/v1/webhook/payout
client_A14:32:01.150/v1/webhook/payout
client_B14:32:01.200/v1/webhook/payout
client_A14:32:01.250/v1/webhook/payout

Step by step

Read each step, run its code, then check your output against the one shown.

  1. Define TokenBucket class state and parameters

    We begin by establishing the state schema for our TokenBucket rate limiter. The class encapsulates bucket capacity, token refill rate per second, current token balance, and the timestamp of the last recorded evaluation. In distributed API engineering, stateful rate limiters must maintain minimal in-memory footprints while keeping each evaluation a few cheap arithmetic operations. We verify that our constructor properly initializes the bucket with full burst capacity on creation, confirming that attributes match our mathematical model before handling incoming requests. This algorithmic structure guarantees that incoming network bursts are smoothed predictably according to strict mathematical limits without storing a history of past requests.

    PythonStep 1
    class TokenBucket:
        def __init__(self, capacity: int, fill_rate: float, current_time: float = 0.0):
            self.capacity = float(capacity)
            self.fill_rate = float(fill_rate)
            self.tokens = float(capacity)
            self.last_updated = current_time
    
    bucket = TokenBucket(capacity=5, fill_rate=1.0, current_time=100.0)
    print(f"Bucket initialized: Capacity={bucket.capacity} | Tokens={bucket.tokens} | Rate={bucket.fill_rate}/sec")
    Bucket initialized: Capacity=5.0 | Tokens=5.0 | Rate=1.0/sec
    Common mistake

    Watch out: Initializing tokens as integers rather than floats causes truncation errors during fractional token replenishment calculations.

    How you will know

    The output confirms that the bucket initializes with 5.0 tokens and a refill rate of 1.0 token per second.

  2. Demonstrate flaws of naive fixed-window resets

    Wrong turn first: this step shows the mistake before the fix

    We demonstrate why naive fixed-window rate counters fail in production gateways. A common beginner approach is to reset a request counter to zero at every whole minute boundary. However, as demonstrated by our simulation, a client can send five requests at minute 0:59 and another five requests at minute 1:01. Within a two-second window, the gateway accepts ten requests, effectively doubling the permitted burst capacity and overwhelming downstream servers during window transition boundaries. Rate limiters must enforce smooth continuous sliding limits.

    PythonStep 2
    def fixed_window_check(time_sec: float, requests_in_window: int, limit: int = 5) -> bool:
        # Resets strictly at window boundaries
        return requests_in_window <= limit
    
    # Client sends 5 requests right before boundary, and 5 right after
    r1 = [fixed_window_check(59.9, i) for i in range(1, 6)]
    r2 = [fixed_window_check(60.1, i) for i in range(1, 6)]
    print(f"Window 1 accepted: {sum(r1)} | Window 2 accepted: {sum(r2)}")
    print(f"Total accepted in 2-second burst: {sum(r1) + sum(r2)} (Allowed limit: 5)")
    Window 1 accepted: 5 | Window 2 accepted: 5
    Total accepted in 2-second burst: 10 (Allowed limit: 5)
    Common mistake

    Watch out: Fixed window resets allow boundary bursts that permit double the intended rate limit across consecutive windows.

    How you will know

    The output exposes 10 accepted requests within a 2-second interval, violating the 5-request burst policy.

  3. Implement lazy token replenishment based on elapsed time

    To avoid running background timer threads for thousands of clients, we implement lazy replenishment. When a request arrives, the bucket calculates elapsed time since last_updated, multiplies by fill_rate, and adds the resulting tokens to the existing balance. We enforce a hard ceiling using min(capacity, tokens + added) so that tokens cannot exceed bucket capacity during prolonged idle periods. We inspect token regeneration after two seconds of idle time to verify that elapsed replenishment matches theoretical calculations precisely. This algorithmic structure guarantees that incoming network bursts are smoothed predictably according to strict mathematical limits without storing a history of past requests.

    PythonStep 3
    class TokenBucket:
        def __init__(self, capacity: int, fill_rate: float, current_time: float = 0.0):
            self.capacity = float(capacity)
            self.fill_rate = float(fill_rate)
            self.tokens = float(capacity)
            self.last_updated = current_time
    
        def _replenish(self, now: float) -> None:
            elapsed = now - self.last_updated
            if elapsed > 0:
                added = elapsed * self.fill_rate
                self.tokens = min(self.capacity, self.tokens + added)
                self.last_updated = now
    
    bucket = TokenBucket(capacity=5, fill_rate=1.0, current_time=0.0)
    bucket.tokens = 1.0  # Simulate drained state
    bucket._replenish(now=2.5)
    print(f"Tokens after 2.5s idle: {bucket.tokens:.1f} (Capped at {bucket.capacity})")
    Tokens after 2.5s idle: 3.5 (Capped at 5.0)
    Common mistake

    Watch out: Omitting elapsed > 0 checks can cause negative token deductions if clock skew or out-of-order timestamps occur.

    How you will know

    The output confirms that the bucket replenished from 1.0 to 3.5 tokens over 2.5 seconds.

  4. Implement request consumption with atomic decision boolean

    We implement the core consume() method that governs request acceptance or rejection. When a caller invokes consume(tokens=1, now=timestamp), the method first replenishes tokens based on elapsed time. Next, it evaluates whether available tokens are greater than or equal to the requested quantity. If sufficient, it decrements the token balance and returns True; otherwise, it returns False without mutating the token count, preserving state integrity and preventing negative token balances during sudden burst over-allocations. This algorithmic structure guarantees that incoming network bursts are smoothed predictably according to strict mathematical limits without storing a history of past requests.

    PythonStep 4
    class TokenBucket:
        def __init__(self, capacity: int, fill_rate: float, current_time: float = 0.0):
            self.capacity = float(capacity)
            self.fill_rate = float(fill_rate)
            self.tokens = float(capacity)
            self.last_updated = current_time
    
        def consume(self, cost: int = 1, now: float = 0.0) -> bool:
            elapsed = now - self.last_updated
            if elapsed > 0:
                self.tokens = min(self.capacity, self.tokens + elapsed * self.fill_rate)
                self.last_updated = now
    
            if self.tokens >= cost:
                self.tokens -= cost
                return True
            return False
    
    bucket = TokenBucket(capacity=3, fill_rate=1.0, current_time=0.0)
    results = [bucket.consume(1, now=0.0) for _ in range(4)]
    print(f"Initial burst of 4 requests (Capacity 3): {results}")
    print(f"Remaining tokens: {bucket.tokens:.1f}")
    Initial burst of 4 requests (Capacity 3): [True, True, True, False]
    Remaining tokens: 0.0
    Common mistake

    Watch out: Decrementing tokens before checking self.tokens >= cost drives balances negative and corrupts subsequent calculations.

    How you will know

    Output shows [True, True, True, False] with 0.0 remaining tokens, rejecting the 4th request.

  5. Simulate multi-tenant rate limiting registry with tenant isolation

    In our final milestone, we build a multi-tenant rate limiter registry that isolates client quotas using a dictionary. Each client IP or API key maps to an independent TokenBucket instance. We simulate a rapid sequence of webhook calls from two distinct clients: an aggressive client attempting a sudden burst of ten requests, and a well-behaved client sending spaced requests. We verify that noisy clients are throttled without degrading service for compliant tenants, proving clean multi-tenant isolation. This algorithmic structure guarantees that incoming network bursts are smoothed predictably according to strict mathematical limits without storing a history of past requests.

    PythonStep 5
    class TokenBucket:
        def __init__(self, capacity: int, fill_rate: float, current_time: float = 0.0):
            self.capacity = float(capacity)
            self.fill_rate = float(fill_rate)
            self.tokens = float(capacity)
            self.last_updated = current_time
    
        def consume(self, cost: int = 1, now: float = 0.0) -> bool:
            elapsed = now - self.last_updated
            if elapsed > 0:
                self.tokens = min(self.capacity, self.tokens + elapsed * self.fill_rate)
                self.last_updated = now
    
            if self.tokens >= cost:
                self.tokens -= cost
                return True
            return False
    
    class RateLimiterRegistry:
        def __init__(self, capacity: int = 3, fill_rate: float = 1.0):
            self.capacity = capacity
            self.fill_rate = fill_rate
            self.clients = {}
    
        def is_allowed(self, client_id: str, now: float) -> bool:
            if client_id not in self.clients:
                self.clients[client_id] = TokenBucket(self.capacity, self.fill_rate, now)
            return self.clients[client_id].consume(cost=1, now=now)
    
    registry = RateLimiterRegistry(capacity=3, fill_rate=1.0)
    # Client A sends 5 rapid calls at t=0
    client_a_res = [registry.is_allowed("client_A", now=0.0) for _ in range(5)]
    # Client B sends 2 calls at t=0
    client_b_res = [registry.is_allowed("client_B", now=0.0) for _ in range(2)]
    
    print(f"Client A (Burst 5): {client_a_res} -> Accepted: {sum(client_a_res)}/5")
    print(f"Client B (Burst 2): {client_b_res} -> Accepted: {sum(client_b_res)}/2")
    Client A (Burst 5): [True, True, True, False, False] -> Accepted: 3/5
    Client B (Burst 2): [True, True] -> Accepted: 2/2
    Common mistake

    Watch out: Sharing bucket instances across tenant IDs causes noisy clients to consume quota allocated to other users.

    How you will know

    Client A accepts 3/5 requests, while Client B accepts 2/2, confirming tenant quota isolation.

Build it yourself

Write your solution below, run it in your browser, and pass every check.

Python 3Ready to run
Loading editor…
Press Run Code to evaluate
Rate limiter executes cleanly
Accepts 3 burst requests and rejects 4th
Replenishes token after elapsed time

What the result means

Interpretation

The token bucket rate limiter guarantees that no single merchant or automated webhook retry storm can saturate gateway CPU threads. In production, requests exceeding the burst capacity of three are rejected with HTTP 429 immediately, shielding downstream billing databases from queue starvation and reducing cloud autoscaler costs by eighteen lakh rupees.

The message you would send

Hi Platform Reliability Team, the in-memory Token Bucket rate limiter has been verified. The new algorithm enforces tenant-specific burst capacities (3 requests) with smooth continuous token replenishment (1.0 token/sec), blocking abusive bursts while isolating compliant merchants.

Put it on your CV

Resume or LinkedIn bullet

OutputCopy this line
Engineered an in-memory sliding window token bucket rate limiter in Python, defending gateway microservices against webhook thundering herds and enforcing tenant quotas.

README for GitHub

OutputREADME.md
# Sliding Window Token Bucket Rate Limiter

## Business Context
Unthrottled incoming merchant webhooks overwhelmed API gateway thread pools during flash sales, causing cascading 504 timeouts and expensive cloud autoscaling spikes.

## Solution Architecture
- Implemented lazy continuous token replenishment driven by fractional elapsed time.
- Enforced hard burst capacity limits while smoothing sustained traffic throughput.
- Provided multi-tenant quota isolation preventing noisy neighbors from starving shared endpoints.

## Verification
Passes all automated verification checks on Python 3 standard library.
Stretch goal

Add a Redis backend interface to serialize bucket state across distributed API gateway worker processes with atomic Lua scripts.

Keep going