Rate Limiting vs Throttling: Two Main Weapons for Controlling Traffic in Large-Scale Systems
10 min read

Rate Limiting vs Throttling: Two Main Weapons for Controlling Traffic in Large-Scale Systems

Real-world traffic never arrives regularly. In one moment, a system can receive tens of thousands of requests at once due to a flash sale, a viral event, or a bot attack. Without a control mechanism, a system theoretically capable of handling high load can collapse just because of one wrong type of traffic. This is where rate limiting and throttling play their roles — both are traffic control mechanisms often used together, but they have different purposes, ways of working, and places in the architecture. Understanding the difference between them is the basic capital for building a system that’s truly resilient.

Two Different Questions

Before getting into implementation details, it’s important to understand that rate limiting and throttling answer two fundamentally different questions.

Rate Limiting  →  "Is this request allowed in?"
Throttling     →  "How fast can this request be processed?"

This difference isn’t just semantics. Its implications directly affect where you place the control, how the system behaves under overload, and what the client experiences.

flowchart TD
    A[Client Request] --> B{Rate Limit Check}
    B -- Limit exceeded --> C[429 Too Many Requests]
    B -- Still within limits --> D{Throttle Check}
    D -- System overloaded --> E[Enter Queue / Delayed]
    D -- Capacity available --> F[Processed by Backend]
    E --> F

Rate limiting sits at the entrance — it decides allowed or not. Throttling sits inside the system — it decides how fast. Relying on only one is an incomplete design.


Rate Limiting

Rate limiting is a mechanism that limits the maximum number of requests from an entity — could be a user, IP address, API key, device, or tenant — within a certain time interval. If the limit is exceeded, the request is immediately rejected with HTTP 429 (Too Many Requests). No delay, no queue — only accept or reject.

Mental Model

Imagine a concert ticket with a capacity of 100 people per hour. Once the quota is met, the 101st person is immediately asked to leave — no matter how important their business is. Rate limiting works exactly like that.

User A's quota: 100 requests/minute
Request #101 within one minute → 429, rejected
Request #1 in the next minute → allowed in

Rate Limiting Algorithms

There are several commonly used algorithms, each with different trade-offs.

Fixed Window Counter is the simplest — the counter resets every fixed time interval (for example every minute). Its main problem is boundary burst: a user can send 100 requests at second 59, then 100 more at second 61, so within a 2-second span there are actually 200 requests that slip through.

sequenceDiagram
    participant Client
    participant Counter
    Client->>Counter: 100 requests (second 59)
    Note over Counter: Window reset
    Client->>Counter: 100 requests (second 61)
    Note over Counter: 200 requests in 2 seconds — the window doesn't detect this

Sliding Window solves this problem by counting requests in a window that moves with the current time. More accurate but more computationally expensive.

Token Bucket fills a “bucket” with tokens periodically. Each request needs one token. Its advantage is supporting controlled bursts — as long as tokens remain, requests can be served even above the normal average. This algorithm is very popular for public APIs.

Leaky Bucket (the hard limit version) works like a leaking bucket — requests enter a fixed-capacity queue and are processed at a constant rate. Requests exceeding the queue capacity are immediately rejected.

flowchart LR
    A[Incoming Request] --> B{Token Available?}
    B -- Yes --> C[Consume 1 Token]
    B -- No --> D[429 Reject]
    C --> E[Process Request]
    F[Periodic Refill] --> B

Rate Limiting Use Cases in Production

Public APIs and SaaS Platforms — Rate limiting is the foundation of tier-based monetization. The free tier gets 60 req/min, the Pro tier 600 req/min, Enterprise gets a custom quota. Without this, free-tier users could consume the entire infrastructure capacity.

Authentication Endpoints — Login, OTP, and password reset are the most critical targets for brute force and credential stuffing. Strict rate limiting on these endpoints can block attacks before they reach the database.

// ANTI-PATTERN: login endpoint without rate limiting
POST /auth/login
→ an attacker could try millions of password combinations

// CORRECT: apply layered rate limiting
POST /auth/login
→ max 5 attempts per IP per minute
→ max 10 attempts per username per hour
→ temporary lockout after the threshold is reached

Multi-Tenant Fairness — In multi-tenant systems, one aggressive tenant can consume shared resources and make other tenants suffer (the noisy neighbor problem). Per-tenant rate limiting maintains logical isolation between them.


Throttling

Throttling is a mechanism for regulating the processing rate of requests when the system approaches or passes its capacity. Unlike rate limiting, which rejects requests, throttling still accepts requests — it just processes them more slowly, delays them, or puts them into a queue.

Mental Model

Imagine a full restaurant. Instead of turning away new guests, they ask guests to wait in the waiting area. The food will still come, it just takes longer. Throttling works like that.

System receives 10,000 requests/second
Backend capacity: 1,000 requests/second
Throttling: 9,000 requests enter the queue, processed gradually
Result: latency rises, but no request is lost

Throttling Mechanisms

Queue-based Throttling puts requests into a queue and processes them according to available capacity. Simple, but risks memory pressure if the queue isn’t bounded.

Concurrency Limiting limits the number of processes allowed to run simultaneously, not the number of requests per time. Suitable for CPU-bound tasks or those requiring exclusive resources.

Adaptive Throttling dynamically adjusts the processing rate based on runtime conditions — CPU usage, memory, latency, or error rate. This is what’s used in modern service meshes like Istio and Envoy.

Backpressure is a pattern where a downstream service signals the upstream to slow down. Instead of a queue silently piling up, the system explicitly communicates its capacity.

sequenceDiagram
    participant ServiceA
    participant ServiceB
    participant Queue

    ServiceA->>ServiceB: 10,000 req/s
    ServiceB->>Queue: Accept all, queue what can't be processed
    Queue-->>ServiceA: Backpressure signal: "I can only handle 1,000/s"
    ServiceA->>ServiceB: Reduce to 1,000 req/s
    Note over ServiceB: System stable, nothing crashes

Throttling Use Cases in Production

Microservices and Service-to-Service Communication — When Service A calls Service B which starts overloading, without throttling Service A will keep retrying and worsen the situation (a retry storm). With throttling, Service A slows down proportionally and cascade failures can be prevented.

// ANTI-PATTERN: unlimited retries when a downstream service is slow
while (!success) {
    response = callServiceB();  // keeps retrying
    // result: Service B sinks further
}

// CORRECT: throttle requests with exponential backoff
response = callServiceB();
if (response.isOverloaded) {
    throttle.wait();  // wait according to the capacity signal
    // Service B has room to recover
}

Background Jobs and Event Consumers — Workers processing Kafka or SQS don’t need to process all messages as fast as possible. Better the backlog rises temporarily than the worker crashes and messages are truly lost.

Financial and Mission-Critical Systems — Payment transactions must not be dropped. In these systems, throttling is the natural choice: latency can be tolerated, but losing transactions can’t.


Fundamental Differences

After understanding both, the differences become very clear.

DimensionRate LimitingThrottling
Main questionAllowed in?How fast?
Fate of excess requestsRejected (429)Delayed / queued
Location in the architectureEdge, API Gateway, CDNInternal service, service mesh
Impact on the clientExplicit errorLatency rises
Main focusSecurity & fairnessStability & resilience
Control natureHard limitSoft control
Visibility to the clientImmediately felt (error)Often invisible
flowchart LR
    subgraph External
        A[Client]
        B[API Gateway<br/>Rate Limiting]
    end
    subgraph Internal
        C[Service Mesh<br/>Throttling]
        D[Service A]
        E[Service B]
        F[Database]
    end

    A --> B
    B -- Request passes --> C
    C --> D
    D --> E
    E --> F

Rate limiting guards the front door — ensuring no entity can flood the system. Throttling guards the internal organs — ensuring internal components don’t destroy each other under high load.


Why Both Are Always Needed

Relying on only one mechanism is a design smell. Here’s why.

Only Rate Limiting Without Throttling

Suppose you limit each user to 100 req/min. If there are 10,000 active users, the backend still receives 1 million requests per minute — a number possibly far above your internal service capacity. Rate limiting protects you from one aggressive user, but not from the combination of many normal users.

// Scenario: rate limit is safe, but the backend can still overload
10,000 users × 100 req/min = 1,000,000 req/min to the backend
Backend capacity: 500,000 req/min
Result: backend overloads even though no user violates the rate limit

Only Throttling Without Rate Limiting

Without rate limiting at the edge, one user or one bot can fill the entire throttling queue. Other legitimate users will be affected because their queue is also full.

// Scenario: throttling exists, but no rate limiting
Bot sends 100,000 requests → fills the queue
Legitimate user sends 1 request → goes to the end of a long queue
Result: the bot gets priority, legitimate users aren't served

The Right Combination

flowchart TD
    A[Client] --> B[API Gateway]

    subgraph B[API Gateway]
        B1[Rate Limit per User/IP/Key]
    end

    B --> C[Load Balancer]

    subgraph C[Service Layer]
        C1[Throttling per Service]
        C2[Backpressure Signal]
    end

    C --> D[Backend Services]
    D --> E[Database / Cache]

    C2 -.-> B1

With this combination:

  • No user can abuse the system from outside
  • No internal component can flood another component
  • The system can do a graceful degradation when load rises

Anti-Patterns to Avoid

// ✗ Anti-pattern 1: Rate limit too strict
Limit: 10 req/min for all endpoints
Result: legitimate users hit the limit during normal operations like page loads
✓ Solution: adjust limits to normal usage patterns; use different tiers
  for different endpoints (authentication vs read vs write)

// ✗ Anti-pattern 2: Throttling without a queue limit
Queue.add(request)  // no max size
Result: the queue grows uncontrollably → memory exhausted → worse crash
✓ Solution: always set a max queue size; requests exceeding queue capacity
  are better rejected than exhausting server memory

// ✗ Anti-pattern 3: Global rate limit without entity differentiation
if (totalRequestCount > 10000) reject()  // all users are affected
Result: one aggressive bot can make all legitimate users hit the limit
✓ Solution: rate limit always per entity (per user, per IP, per API key),
  not per total request to the system

// ✗ Anti-pattern 4: Throttling without monitoring
Throttle active but no alerts or metrics
Result: the team doesn't know when the system starts struggling until it's too late
✓ Solution: monitor queue length, throttle activation rate, and p99 latency
  as system health signals

Implementation Checklist

RATE LIMITING:
  □ Applied at the edge layer (API Gateway / reverse proxy)
  □ Granularity per entity: user ID, IP, API key, or tenant
  □ Algorithm chosen per need: token bucket for bursts,
    sliding window for high accuracy
  □ 429 responses include the Retry-After header
  □ Different limits for different endpoints (authentication stricter)
  □ Different limits for different user tiers (free vs pro vs enterprise)

THROTTLING:
  □ Applied in service-to-service calls and background workers
  □ Queues have a maximum limit (no infinite queue)
  □ There's a backpressure mechanism to the upstream
  □ Adaptive throttling considers CPU, latency, and error rate
  □ Timeouts are configured so requests don't wait too long

MONITORING:
  □ Rate limit hit metrics per endpoint per time
  □ Queue length and queue wait time metrics
  □ Alerts when the throttle activation rate exceeds the normal threshold
  □ p50, p95, p99 latency dashboards per service
  □ Limits and capacities documented in the API documentation

Summary

  • Rate limiting answers “allowed or not” — it sits at the edge, rejects requests exceeding the quota with HTTP 429, and focuses on protection and fairness.
  • Throttling answers “how fast” — it sits inside the system, slowing or queuing requests when capacity is full, and focuses on stability.
  • Token bucket is the most flexible rate limiting algorithm because it supports controlled bursts; suitable for public APIs.
  • Backpressure is the healthiest throttling pattern in microservice architectures — the downstream signals the upstream, rather than silently piling up load.
  • Both are always needed — rate limiting protects against external abuse, throttling protects against internal overload. Relying on only one is an incomplete design.
  • The most common anti-patterns: rate limits too strict ruin user experience; throttling without a queue limit can cause a worse crash than the original problem.
  • Always monitor queue length, throttle activation rate, and latency percentiles — these three are the earliest indicators that the system is starting to struggle.

Portfolio