DEV Community

Cover image for Rate Limiting: A Practical Guide
Swapnoneel Saha
Swapnoneel Saha

Posted on

Rate Limiting: A Practical Guide

Rate Limiting: A Practical Guide

TL;DR

  • Rate limiting protects upstream infrastructure, enforces fair tenant allocation, and controls compute spend by bounding request velocity.
  • Traditional algorithms like token bucket, leaky bucket, and sliding window counter offer distinct trade-offs between burst tolerance and algorithmic overhead.
  • AI and LLM workloads require multi-dimensional rate limiting that tracks both requests per minute (RPM) and tokens per minute (TPM) across nested consumer tiers.
  • Implementing rate limiting at the infrastructure layer through Bifrost, a high-performance open-source AI gateway written in Go by Maxim AI, decouples policy enforcement from backend application code while introducing only 11 microseconds of overhead.

Rate limiting is a traffic management mechanism that constrains how many requests or units of computational work a client can submit to a service within a defined time window. In modern software architectures, exposing an unmetered API invites cascading service degradation, unexpected billing spikes, and vulnerability to denial-of-service traffic. The rise of generative AI and large language models has amplified these failure modes, shifting the problem from simple HTTP request counts to multi-dimensional token budgets and upstream provider quotas. This guide examines the fundamental algorithms of rate limiting, standard HTTP communication protocols, distributed architectures, and how modern engineering teams handle rate limiting across both standard web services and LLM infrastructure.

What is Rate Limiting and Why APIs Require It

Rate limiting restricts the volume of inbound or outbound network traffic by measuring client consumption against predetermined thresholds and rejecting calls that exceed those allocations. When an entity exhausts its budget, the receiving server generally returns an HTTP 429 Too Many Requests status code, instructing the client to pause before attempting further execution.

API providers implement rate limiting to achieve four primary operational objectives:

  • Preventing resource starvation and outages: Heavy bursts of concurrent traffic can overwhelm database connection pools, memory allocations, and worker threads, causing latency degradation or full service outages for all users.
  • Enforcing multi-tenant fairness: In shared cloud environments, noisy neighbors can consume an unfair proportion of backend capacity; rate limiting assigns distinct throughput ceilings to individual tenants.
  • Controlling operational expenses: Many cloud services and third-party APIs bill per transaction. In AI systems, unconstrained consumption of inference APIs can exhaust monthly organizational budgets within hours.
  • Mitigating credential abuse and scraping: Rate limiting introduces friction against automated credential stuffing, brute-force attacks, and large-scale web scraping bots attempting to extract proprietary data.

Rate limits operate across multiple granularities. Network engineers frequently enforce IP-based rate limits at edge proxies or Web Application Firewalls (WAFs) to filter volumetric bot attacks. Application engineers implement authenticated rate limits keyed to user identifiers, organization IDs, or API keys to align technical quotas with commercial pricing tiers.

Core Rate Limiting Algorithms Compared

Choosing the correct rate limiting algorithm dictates how a system reacts to sudden traffic spikes, how much memory is spent maintaining state, and whether clients experience sudden cliff-edge rejections. There are five primary algorithms used across modern infrastructure.

1. Token Bucket

The token bucket algorithm maintains a centralized bucket for each client with a fixed maximum capacity of tokens. Tokens are continuously added to the bucket at a constant fill rate until the bucket is full. When an incoming request arrives, the limiter checks whether the bucket contains enough tokens to satisfy the cost of the request (typically one token for standard HTTP calls, or multiple tokens corresponding to payload size). If tokens are available, they are deducted and the request proceeds; if the bucket is empty, the request is rejected.

Token bucket allows controlled bursts of traffic up to the maximum capacity of the bucket while guaranteeing that the long-term request rate cannot exceed the token refill rate. For this reason, cloud platforms and AI providers frequently rely on token bucket algorithms.

2. Leaky Bucket

The leaky bucket algorithm processes requests through a first-in, first-out (FIFO) queue of fixed size. Regardless of the rate at which requests enter the bucket, they drain and execute at a smooth, constant output rate. If incoming traffic arrives faster than the leak rate, the queue fills up; any requests arriving when the queue is completely full overflow and are dropped immediately.

Leaky bucket prioritizes downstream system stability above all else by eliminating traffic bursts entirely. It is commonly implemented for outbound webhook deliveries, data pipeline ingestion, and legacy systems that require constant, predictable throughput.

3. Fixed Window Counter

The fixed window counter algorithm divides time into discrete, static blocks (for example, 60-second intervals starting on the minute). Each client possesses an integer counter that increments with each request during that window. Once the counter reaches the configured ceiling, subsequent requests within that window are denied. When the clock crosses into the next window, the counter resets to zero.

While the fixed window algorithm requires minimal memory (storing only an integer and a timestamp), it suffers from a significant boundary flaw: a client can send its entire quota in the final seconds of one window and send another full quota in the opening seconds of the next window. This results in double the permitted throughput across the window boundary, which can destabilize fragile backends.

4. Sliding Window Log

The sliding window log algorithm tracks the exact timestamp of every request executed by a client in a sorted log (often stored as a Redis sorted set). When a new request arrives, the limiter prunes all timestamps older than the current time minus the window duration, then counts the remaining entries. If the log size remains below the threshold, the current timestamp is appended and the request is permitted.

Sliding window log provides near-perfect mathematical accuracy and eliminates the boundary surge issues of fixed windows. However, its memory consumption scales linearly with request volume, making it prohibitively expensive for high-throughput public APIs.

5. Sliding Window Counter

The sliding window counter algorithm blends the low memory footprint of the fixed window approach with the smoothing accuracy of the sliding log. It divides time into fixed windows but calculates current usage by combining the count of the current window with a weighted percentage of the previous window's count based on the current timestamp.

For example, if the current window is 30% complete, the algorithm weighs 70% of the previous window's request count alongside 100% of the current window's request count. This sliding calculation smooths out the boundary bursts while requiring only two integer counters per client.

Algorithm Memory Complexity Burst Tolerance Edge Boundary Fairness Operational Overhead Ideal Use Case
Token Bucket Low ($O(1)$) High (up to bucket capacity) High (constrained by refill rate) Low General-purpose APIs, LLM request and token limits
Leaky Bucket Moderate ($O(N)$ queue size) None (enforces strict constant rate) High (constant outflow) Moderate Outbound webhooks, asynchronous data pipelines
Fixed Window Counter Minimal ($O(1)$ integer) High (resets cleanly each period) Poor (allows $2\times$ burst at boundaries) Minimal Low-traffic internal services, simple billing caps
Sliding Window Log High ($O(N)$ requests logged) Low (strictly enforces exact rolling window) Perfect High (memory and sorting compute) Low-volume, high-security endpoints (e.g., auth)
Sliding Window Counter Low ($O(1)$ two integers) Moderate (smooths burst transitions) High (weighted rolling estimate) Low High-throughput distributed web APIs

Five distinct mechanical hourglasses and sculpted water vessels demonstrating varying rates of fluid drainage and filtra

HTTP Status Codes and Standard Rate Limit Headers

When an API client crosses an operational threshold, clear communication between the server and the consuming application prevents client-side retries from exacerbating the outage.

The 429 Too Many Requests Status Code

Standardized in RFC 6585 (Additional HTTP Status Codes), the 429 Too Many Requests response indicates that the client has submitted too many requests within a given duration. The RFC specifies that response representations should explain the condition and provide guidance on remediation.

Response Headers: De Facto Conventions vs. IETF Standards

Historically, API vendors invented their own header conventions to communicate consumption limits, typically standardizing on X-RateLimit-* prefixes:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1772635200
Enter fullscreen mode Exit fullscreen mode

To eliminate ecosystem fragmentation, the Internet Engineering Task Force (IETF) HTTPAPI working group developed the RateLimit Header Fields for HTTP specification. This emerging standard establishes unified header definitions using HTTP structured fields:

  • RateLimit-Limit: The maximum number of allowed requests within the quota period.
  • RateLimit-Remaining: The number of requests remaining in the current window.
  • RateLimit-Reset: The number of seconds remaining until the quota resets.
  • RateLimit-Policy: The explicit policy parameters governing the quota (e.g., 100;w=60 for 100 requests per 60-second window).
  • Retry-After: Standardized in RFC 9110, this header indicates the exact number of seconds (or HTTP date) the client must wait before making another attempt.

Implementing Client-Side Exponential Backoff with Jitter

When a client encounters a 429 error, retrying immediately at fixed intervals leads to the "thundering herd" problem, where synchronized clients repeatedly bombard recovering infrastructure. Production clients should implement exponential backoff combined with randomized jitter.

The following Python example demonstrates a robust retry loop that respects server rate limit headers and falls back to truncated exponential backoff with full jitter:

import time
import random
import requests

def request_with_backoff(url: str, headers: dict, max_retries: int = 5) -> requests.Response:
    base_delay = 1.0
    max_delay = 32.0

    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)

        if response.status_code != 429:
            return response

        # Check for Retry-After header
        retry_after = response.headers.get("Retry-After")
        if retry_after and retry_after.isdigit():
            sleep_duration = float(retry_after)
        else:
            # Full Jitter: Sleep = random_between(0, min(max_delay, base_delay * 2^attempt))
            calculated_delay = min(max_delay, base_delay * (2 ** attempt))
            sleep_duration = random.uniform(0, calculated_delay)

        time.sleep(sleep_duration)

    raise RuntimeError(f"Request failed after {max_retries} retries due to rate limiting.")
Enter fullscreen mode Exit fullscreen mode

Architecture: Local vs. Distributed Rate Limiting

The technical implementation of rate limiting depends on whether an application runs as an isolated single instance or a horizontally scaled fleet of microservices.

Local In-Memory Rate Limiting

In a single-process service, rate limiting can be executed entirely in memory using local data structures, mutexes, or atomic primitives. This approach offers negligible latency (under 1 microsecond per check) and eliminates external database dependencies.

However, in-memory limiters fail in distributed environments. If an API deployment scales to ten container replicas behind a round-robin load balancer, a client with an in-memory limit of 100 requests per minute can effectively execute up to 1,000 requests per minute if their traffic is evenly distributed across all nodes. Conversely, if a single node receives an uneven cluster of traffic, a client may be throttled prematurely.

Distributed Rate Limiting with Redis and Lua

To enforce consistent global limits across distributed instances, platforms decouple rate limit state into a shared, centralized cache. Redis is the industry standard for distributed rate limiting due to its in-memory performance and atomic command primitives.

Naively executing separate Redis commands (e.g., reading a key, incrementing a count, and setting an expiration) introduces critical race conditions under concurrent load. Two simultaneous requests might read a counter at 99, both determine that capacity remains, increment to 100, and bypass the limit.

To guarantee atomicity without locking performance penalties, teams execute rate limiting algorithms inside Redis using Lua scripts. Because Redis executes Lua scripts as a single atomic transaction, no other operations can interleave.

Here is an atomic implementation of a token bucket algorithm in Redis Lua:

-- Keys: [1] bucket key, [2] timestamp key
-- Args: [1] max_capacity, [2] refill_rate (tokens/sec), [3] current_time (unix seconds), [4] cost (tokens)

local bucket_key = KEYS[1]
local time_key = KEYS[2]

local capacity = tonumber(ARGV[1])
local refill_rate = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local cost = tonumber(ARGV[4])

-- Retrieve current state
local current_tokens = tonumber(redis.call('get', bucket_key))
local last_updated = tonumber(redis.call('get', time_key))

if current_tokens == nil then
    current_tokens = capacity
    last_updated = now
else
    -- Compute refilled tokens based on delta time
    local delta = math.max(0, now - last_updated)
    local tokens_to_add = delta * refill_rate
    current_tokens = math.min(capacity, current_tokens + tokens_to_add)
    last_updated = now
end

-- Check capacity
if current_tokens >= cost then
    current_tokens = current_tokens - cost
    redis.call('set', bucket_key, current_tokens)
    redis.call('set', time_key, last_updated)
    return {1, current_tokens} -- Allowed: 1, remaining tokens
else
    return {0, current_tokens} -- Denied: 0, remaining tokens
end
Enter fullscreen mode Exit fullscreen mode

While distributed rate limiting solves consistency, it introduces a network round-trip (typically 1 to 5 milliseconds) into the critical path of every incoming HTTP request. For systems operating at tens of thousands of requests per second, caching engines can become an expensive bottleneck.

The Shift to Multi-Dimensional Rate Limiting in AI Workloads

Standard REST APIs measure consumption exclusively through request counts (e.g., 100 requests per minute). Generative AI and LLM APIs break this mental model. In an AI application, treating all HTTP requests identically creates catastrophic blind spots.

Consider two separate requests sent to an OpenAI or Anthropic model:

  • Request A: A lightweight intent classifier sending 40 prompt tokens and receiving a 5-token response.
  • Request B: An agentic retrieval pipeline sending a 100,000-token codebase context and generating 2,000 completion tokens.

Both payloads register as exactly one HTTP request, yet Request B consumes thousands of times more GPU memory, hardware execution time, and provider cost.

Consequently, upstream model providers enforce multi-dimensional limits simultaneously:

  • Requests Per Minute (RPM): Caps on raw HTTP call velocity.
  • Tokens Per Minute (TPM): Caps on the aggregate volume of input and output tokens consumed per rolling window.
  • Tokens Per Day (TPD): Daily volume ceilings preventing cumulative exhaustion.
  • Concurrent Connections: Limits on the number of active streaming generations allowed in flight.

If an application exceeds any single dimension, the provider returns a 429 response. In production environments where multiple internal services or engineering teams share a single provider API key, one unoptimized prompt run by a background service can instantly exhaust the organizational TPM quota, causing downtime for customer-facing interfaces.

Addressing this requires a two-sided rate limiting architecture: internal quotas enforced on consumers before requests leave the infrastructure, paired with real-time routing that adapts to upstream provider ceilings.

Implementing Gateway-Level Rate Limiting with Bifrost

Decoupling rate limiting and quota enforcement from individual microservices allows organizations to standardize policies across internal applications. Bifrost is a high-performance, open-source AI gateway developed by Maxim AI that serves as an intelligent control plane between client applications and external model providers.

+-------------------------------------------------------------------------+
|                          Client Applications                            |
|             (Web Services, Microservices, Background Workers)           |
+-------------------------------------------------------------------------+
                                     |
                                     |  HTTP Request (Bearer sk-bf-*)
                                     v
+-------------------------------------------------------------------------+
|                           Bifrost AI Gateway                            |
|                                                                         |
|  +--------------------+  +----------------------+  +-----------------+  |
|  |    Virtual Keys    |  | Token & Request Rate |  |  Hierarchical   |  |
|  |   Authentication   |  |   Limit Engine       |  |     Budgets     |  |
|  +--------------------+  +----------------------+  +-----------------+  |
|                                    |                                    |
|                      Pass / Fail Capacity Check                         |
|                                    v                                    |
|  +--------------------+  +----------------------+  +-----------------+  |
|  |  Semantic Caching  |  | Multi-Key Balancing  |  |    Failover     |  |
|  |       Engine       |  |      & Routing       |  |     Chains      |  |
|  +--------------------+  +----------------------+  +-----------------+  |
+-------------------------------------------------------------------------+
             |                                       |
             v                                       v
+------------------------+              +-------------------------+
| Upstream LLM Provider  |              |  Fallback LLM Provider  |
|  (e.g., Primary Model) |              |  (e.g., Secondary Model)|
+------------------------+              +-------------------------+
Enter fullscreen mode Exit fullscreen mode

Rather than burdening each application with token counting, distributed locks, and retry state machines, engineers place Bifrost in front of LLM traffic as a drop-in replacement for native SDKs, modifying only the base URL.

Virtual Keys and Hierarchical Limits

Bifrost manages rate limits and cost controls through virtual keys. Instead of distributing raw provider credentials (such as proprietary OpenAI or Anthropic API keys) across distributed microservices, operators issue scoped virtual keys (sk-bf-*) to specific applications, teams, or customers.

Bifrost enforces governance hierarchically across four structural tiers:

$$\text{Customer Budget} \longrightarrow \text{Team Budget} \longrightarrow \text{Virtual Key Quota} \longrightarrow \text{Provider Limit}$$

At the virtual key tier, operators configure independent rate limits covering both request counts and token quantities:

  • Request limits: The maximum permitted calls per second, minute, hour, or day.
  • Token limits: The maximum aggregate input and output tokens permitted per time period.
  • Reset duration: The rolling or calendar-aligned window across which counters evaluate.

When a client application sends a payload, Bifrost checks the active virtual key against all inherited ceilings. If a team has exhausted its hourly token limit, Bifrost intercepts the call at the gateway layer and returns an HTTP 429 response before the request ever reaches an upstream provider, protecting downstream cloud spend.

The following configuration example illustrates how an administrator defines a virtual key with explicit request and token rate limits:

{
  "name": "analytics-pipeline-key",
  "team_id": "team_data_platform",
  "rate_limits": [
    {
      "type": "requests",
      "max_limit": 120,
      "reset_duration": "1m"
    },
    {
      "type": "tokens",
      "max_limit": 250000,
      "reset_duration": "1m"
    }
  ],
  "budget": {
    "max_limit": 500.00,
    "reset_duration": "1M",
    "calendar_aligned": true
  }
}
Enter fullscreen mode Exit fullscreen mode

A central glowing control junction dynamically redirecting converging geometric pulses of light across multiple branchin

Mitigating Upstream 429s via Intelligent Routing

While internal rate limits protect budgets, applications must also navigate rate limits imposed by upstream model APIs. When an upstream provider returns a 429 status code due to account-level saturation, standard applications fail outright.

Bifrost resolves this through multi-key pooling and automatic fallbacks. Platform engineers can load balance traffic across multiple API keys tied to distinct provider accounts. If a primary key encounters an upstream rate limit, Bifrost rotates traffic to secondary credentials or immediately reroutes the payload to an equivalent model on an alternative provider (such as falling back from Anthropic Claude to AWS Bedrock or Azure OpenAI) with zero downtime.

Furthermore, integrating semantic caching directly within the gateway intercepts semantically identical prompts, serving responses from a local vector cache and preventing unnecessary token consumption against upstream quotas.

Ultra-Low Latency Performance

A critical challenge of gateway-level rate limiting is latency overhead. Traditional API proxies written in interpreted languages or relying heavily on unoptimized external database calls can add tens of milliseconds to every request.

Bifrost is implemented in Go and keeps critical governance, budget, and rate limit states synchronized in memory. Published benchmarks demonstrate that Bifrost introduces only 11 microseconds of overhead per request under sustained loads of 5,000 requests per second. For high-availability deployments requiring multi-node coordination, Bifrost Enterprise utilizes an optimized implementation of the RAFT consensus protocol for state synchronization across clusters, avoiding continuous database round-trips while preserving distributed rate limit accuracy.

Endpoint AI Governance and Rate Limit Enforcement

A frequent blind spot in enterprise rate limiting is shadow AI consumption occurring on employee workstations. While platform engineers can enforce strict token limits on production backend services, developers and knowledge workers frequently use local coding agents, desktop clients, and command-line interfaces (such as Claude Code, Cursor, or terminal utilities) using shared corporate credentials or personal accounts.

Beyond routing, Bifrost applies governance and security controls (virtual keys, budgets, guardrails, audit logs) centrally, and Bifrost Edge extends that same governance and security to AI traffic on employee machines, with endpoint enforcement on each device.

Operating as an endpoint companion to the central gateway, Bifrost Edge operates natively on macOS, Windows, and Linux to intercept local AI traffic and ensure it routes through the organization's central policy plane. Through app governance, security administrators discover ungoverned AI tools, block unvetted applications, and ensure that developer coding assistants inherit the exact same virtual key rate limits, token quotas, and data access policies configured at the gateway level.

Best Practices for Designing Resilient Rate Limiting

Constructing an effective rate limiting infrastructure requires balancing strict system defense with developer ergonomics. When architecting rate limiting policies, adhere to the following best practices:

  • Differentiate limits by consumer tier: Avoid establishing a single monolithic rate limit across all endpoints. Assign distinct quotas based on authentication status, commercial subscription tiers, and endpoint computational intensity (e.g., an expensive data export endpoint requires tighter limits than a health check).
  • Surface granular telemetry: Rate limiting systems should expose comprehensive metrics into monitoring platforms. Bifrost provides native Prometheus metrics and distributed tracing via OpenTelemetry, allowing platform teams to observe rate-limited events, token usage velocity, and rejected request counts in real time.
  • Fail open vs. fail closed deliberately: Determine whether rate limiting components should fail open or fail closed during an infrastructure outage. For mission-critical internal microservices, failing open prevents a crashed caching node from taking down entire product flows. For billing-critical external APIs or expensive LLM endpoints, failing closed protects the organization from catastrophic financial liability.
  • Provide actionable error messages: When returning an HTTP 429 response, include explicit metadata indicating why the request was blocked, which specific limit was breached (e.g., tokens vs. requests), and the precise timestamp when the caller may resume execution.
  • Separate client-side concurrency from throughput: In addition to measuring requests per minute, bound maximum concurrent requests per tenant. A client submitting 60 requests in one millisecond creates severe thread pool contention, even if their nominal limit is 60 requests per minute.

Frequently Asked Questions

What is the primary difference between rate limiting and throttling?

Rate limiting is a policy that defines hard maximum boundaries on the volume of requests a client can execute within a specific time window, typically rejecting excess requests with an HTTP 429 status code. Throttling is an active traffic-shaping mechanism that dynamically slows down request processing or delays responses to keep execution rates within acceptable operational thresholds without immediately dropping connections.

How does the token bucket algorithm handle sudden traffic bursts?

The token bucket algorithm handles bursts by allowing clients to consume accumulated tokens up to the total capacity of the bucket in a single instant. Once those tokens are depleted, subsequent requests are constrained strictly to the steady-state refill rate at which new tokens are added back into the bucket over time.

Which HTTP status code should an API return when rate limits are exceeded?

An API should return the HTTP 429 Too Many Requests client error status code, as defined in RFC 6585. Servers should pair this status code with a Retry-After header specifying the duration in seconds that the client must pause before attempting another request.

Why do large language model APIs require token-based rate limiting instead of request-based rate limiting?

LLM APIs require token-based rate limiting because computational resource utilization varies dramatically across individual HTTP calls. A single request with a massive context window can consume hundreds of thousands of tokens and multiple seconds of GPU compute, while another consumes only a few tokens. Limiting only request counts allows heavy payloads to exhaust backend GPU resources and operational budgets.

What is the advantage of using Redis Lua scripts for distributed rate limiting?

Redis Lua scripts execute as single atomic transactions directly inside the Redis engine. This guarantees that checking counter thresholds, incrementing request counts, and setting key expiration times occur without race conditions, preventing concurrent clients from bypassing rate limits across horizontally distributed application instances.

How does Bifrost handle upstream provider rate limits?

Bifrost mitigates upstream provider rate limits by maintaining pools of API keys, monitoring provider capacity, and automatically failing over to backup keys or alternate providers when an upstream service returns an HTTP 429 error. This failover mechanism operates transparently at the gateway layer, shielding client applications from third-party outages and quota exhaustion.

Next Steps in API and AI Rate Limiting

Rate limiting is an indispensable pillar of reliable distributed systems. As architectures evolve to incorporate expensive AI models and high-concurrency microservices, managing computational traffic requires transitioning from simple fixed window counters to multi-dimensional, low-latency infrastructure enforcement.

Engineering organizations seeking to govern LLM rate limits, eliminate upstream 429 errors, and enforce hierarchical token budgets across teams can evaluate Bifrost by reviewing the open-source repository, consulting the LLM Gateway Buyer's Guide, or scheduling a Bifrost demo.

Sources

Top comments (0)