WritingAPI Rate Limiting: Strategies, Algorithms, and Implementation Guide — Clixo
6 min readapi-design, rate-limiting, backend, security

API Rate Limiting: Strategies, Algorithms, and Implementation Guide

A practical guide to API rate limiting strategies — token bucket, sliding window, fixed window — with implementation patterns and what to return when limits are hit.

Every API that is accessible over the internet needs rate limiting. Without it, a single misbehaving client — whether through a bug, a misconfigured retry loop, or deliberate abuse — can exhaust your database connections, inflate your infrastructure bill, or degrade service for every other consumer.

Rate limiting is not complicated to reason about, but the implementation details matter. The wrong algorithm in the wrong place gives you either false protection or broken client experiences.

Why API Rate Limiting Strategies Differ

The core question in rate limiting is: "how do I measure consumption fairly and efficiently?" The answer depends on what you are protecting against:

  • Burst protection: prevent a client from sending thousands of requests in a second
  • Sustained rate control: prevent a client from exceeding a daily or hourly quota
  • Per-resource limits: protect expensive operations independently from cheap ones

Different algorithms handle these differently.

The Main Rate Limiting Algorithms

Fixed Window

Divide time into fixed intervals (1 minute, 1 hour). Count requests per interval. Reset the counter at the start of each interval.

Implementation: a Redis counter keyed by rate:{client_id}:{window_start}, incremented on each request, expired at the end of the window.

The problem: a client can make all their requests in the last second of one window and the first second of the next, getting double the nominal rate for a two-second burst. This is the "thundering herd at window boundary" problem.

Use it when: simplicity matters more than precision, or when the granularity is coarse enough (daily limits) that the boundary burst is acceptable.

Sliding Window Log

Track every request timestamp in a sorted set. Count how many timestamps fall within the last N seconds. Reject if the count exceeds the limit.

The upside: accurate. No boundary burst. True "N requests in the last M seconds" semantics.

The downside: memory usage is proportional to requests per client, not per window. For high-volume clients, the sorted set grows large.

Use it when: accuracy is critical and the request volume per client is bounded.

Sliding Window Counter (Approximate)

A practical middle ground. Keep counters for the current and previous fixed windows. Calculate an approximate sliding window count by weighting the previous window's counter based on how far into the current window you are:

estimated = previous_count * (1 - elapsed_fraction) + current_count

This is accurate within a few percent and uses constant memory. Most production rate limiters use this approach.

Token Bucket

Each client has a "bucket" with a maximum capacity. Tokens are added at a constant rate. Each request consumes one token. If the bucket is empty, the request is rejected.

The key property: it allows bursts up to the bucket capacity while enforcing the average rate over time. A client with a bucket of 50 that refills at 10 per second can burst to 50 immediately after a quiet period, then is limited to 10 per second thereafter.

Use it when: you want to allow reasonable bursts while still enforcing a sustainable average rate. Most user-facing APIs benefit from this model because it handles the natural bursty usage patterns of real applications.

Leaky Bucket

Requests enter a queue. They are processed at a constant rate. If the queue fills up, new requests are dropped.

The key property: output is always at a steady rate regardless of input bursts. Good for protecting downstream systems that cannot handle variable load.

Use it when: you need smooth output rates, not just input rate limits — typically for job queues or outbound webhooks, not incoming API requests.

What to Return When a Limit Is Hit

The standard response for a rate-limited request is 429 Too Many Requests. This should include:

HTTP/1.1 429 Too Many Requests
Retry-After: 37
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1733133600
  • Retry-After tells the client how many seconds to wait before retrying. This is essential for well-behaved clients to back off automatically.
  • X-RateLimit-Limit tells the client what the limit is.
  • X-RateLimit-Remaining tells them how many requests they have left in the current window.
  • X-RateLimit-Reset tells them when the window resets (Unix timestamp).

Always include Retry-After. A 429 without it forces clients to implement their own guessing logic, and they will often guess badly.

Where to Apply Rate Limits

At the API gateway, not in application code. Application-level rate limiting requires a shared counter store (typically Redis) and adds latency to every request. An API gateway (Kong, AWS API Gateway, Nginx, Traefik) handles rate limiting before the request reaches your service, at much lower overhead.

Per client, not globally. Global rate limits protect against total traffic floods but do not prevent a single client from crowding out others. Identify clients by API key, OAuth client ID, or IP address and apply per-client limits.

Per endpoint, not just globally per client. A request to GET /users costs almost nothing. A request to POST /reports/generate triggers a multi-second query. Rate limit them separately. A client should not be able to exhaust their API quota on expensive endpoints before making a single cheap read.

Rate Limiting and Idempotency Keys

Rate limits apply to requests, not to outcomes. If a client retries a request with an idempotency key, that retry counts against their rate limit. This is correct behavior. The idempotency key protects against duplicate side effects, not duplicate consumption.

Communicating Limits to API Consumers

Rate limits should be documented explicitly:

  • Limit per time window, per endpoint tier
  • How clients are identified (API key, OAuth scope, IP)
  • What happens when the limit is exceeded
  • How to request a higher limit

Undocumented rate limits that clients discover by hitting them in production create unnecessary support load and erode trust in the API.

For API systems where rate limiting, quotas, and fair usage are part of the product design, Clixo builds the backend infrastructure that enforces these guarantees reliably.