Detect and block abuse without blocking real users
Pick between token-bucket and sliding-window
The why
Without rate limiting, one angry user with a while(true) loop can take down your . One scraper can drain your database. One credential-stuffer can brute-force 100,000 passwords. Rate limiting is not a nice-to-have — it's the difference between a resilient API and a fragile one.
Core concepts
Rate limiting algorithms
Algorithm
How it works
Pros
Cons
Fixed Window
Count requests in fixed time buckets (per minute)
Simple to implement
Burst at boundary: 100 req at 12:00:59 + 100 at 12:01:00 = 200 in 2 seconds
Sliding Window Log
Store timestamp of every request. Count recent ones.
Accurate
Memory-intensive (stores every request)
Sliding Window (approximate)
Weighted count based on previous window's usage
Accurate enough, memory-efficient
Slightly complex arithmetic
Token Bucket
Tokens refill at a fixed rate. Each request consumes a token.
// Detect credential stuffing: many different usernames from same IPasync function detectCredentialStuffing(ip) { const key = `login_attempts:${ip}:${Math.floor(Date.now() / 60000)}`; const uniqueUsers = await
Real-world example: How GitHub handles rate limiting
Headers in every response:X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset (epoch seconds).
Secondary limits (abuse detection): Even within the primary limit, GitHub detects abusive patterns (rapid consecutive requests to the same endpoint) and issues a secondary rate limit.
Conditional requests encouraged: Use If-None-Match (ETag) and If-Modified-Since — requests that return 304 Not Modified don't count against your limit.
Common mistakes
Mistake
Why it's wrong
What to do instead
Fixed window without burst protection
Boundary problem: 2x limit in 2 seconds at window edge
Sliding window or token bucket
Rate limit by IP only
Carrier-grade NAT: thousands of users share one IP. One bad actor blocks everyone.
Rate limit by user ID (authenticated) + IP (anonymous)
No Retry-After header
Client has no idea how long to wait before retrying
Always include Retry-After in 429 responses
Hardcoded limits
Tuning requires redeploy
Environment variables or database-backed configuration
Rate limiter that blocks your own health checks
/health endpoint gets rate-limited → orchestrator thinks the app is down
Exclude health check and metrics endpoints from rate limiting
No monitoring of rate limit hits
You don't know if limits are too strict (blocking real users) or too loose (not stopping attackers)
Log and alert on 429 rate. Dashboard of limit consumption.
Production notes
Use Redis with Lua scripts for atomic rate limit operations. The check-and-increment must be atomic, or concurrent requests can exceed the limit.
Set different limits per endpoint. Login: 5/15min. API: 100/min. Admin: 500/min. File upload: 10/min.
Respect Retry-After. Good API consumers will back off. Bad ones will get blocked by your WAF eventually.
In-memory fallback. If Redis is down, fall back to an in-memory rate limiter with a warning log. Never fail open.
Distributed rate limiting. If you have 10 API servers, Redis is the shared . Without it, each server has its own counter — users can send 10x the limit by hitting different servers.
Security notes
Rate limit is a security control, not just a performance control. It prevents brute force, credential stuffing, DDoS, and scraping.
Don't leak rate limit information unnecessarily. 429 responses are fine. Detailed error messages ("You have 3 attempts remaining") for login endpoints are a tradeoff: good UX, but helps attackers pace their attacks.
Exercises
Beginner
Implement a fixed-window rate limiter (in-memory) that limits to 10 requests per minute. Test with a loop that sends 15 rapid requests — verify the last 5 get 429.
Add the standard rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) to your responses.
Intermediate
Implement a sliding-window rate limiter using Redis sorted sets. Test boundary behavior: 100 requests at 59 seconds + 100 at 1 second should correctly limit.
Implement tiered rate limiting: anonymous users get 20/min, authenticated users get 100/min, admin users get 1000/min.
Advanced
Implement token bucket rate limiting with Redis. Compare its burst-handling behavior with the sliding window approach. Which is better for your use case?
Build a rate limit dashboard: show current consumption per user, detect users approaching their limit, and provide an admin API to temporarily increase limits.
Interview questions
Beginner
Why is rate limiting important? Prevents abuse (brute force, credential stuffing), protects against DDoS, ensures fair resource usage across users, and prevents a single user from degrading service for everyone else.
What HTTP status code does rate limiting return? 429 Too Many Requests. Should include a Retry-After header telling the client when to retry.
What's the problem with fixed-window rate limiting? The boundary problem — a user can send 2x the limit in a short burst by straddling the window boundary (e.g., max at second 59, then max again at second 1).
Senior
Compare sliding window vs token bucket for rate limiting. Sliding window: counts requests in the last N seconds. Accurate, simple concept. Token bucket: tokens refill at a constant rate, bursts are allowed up to bucket capacity. Token bucket is better for allowing short bursts while maintaining a long-term average. Sliding window is better for strict per-second/minute limits.
How do you rate limit in a distributed system with multiple API servers? Use a shared state store (Redis) with atomic operations (Lua scripts). Each server checks and updates the shared counter. Without shared state, an attacker can exceed the limit by distributing requests across servers. Fall back to local rate limiting if Redis is unavailable (with degraded accuracy).
How would you design rate limiting for a multi-tenant SaaS where tenants have different plans? Per-tenant limits stored in the database (cached in Redis). On each request: identify tenant from API key or , look up their tier's limit, check against their current usage in Redis. Allow tenant admins to view their usage. Allow temporary limit increases via a support/admin API. Bill for overages or hard-cutoff at limit depending on the business model.
Summary
Rate limiting is essential for API resilience and security. Use sliding window (Redis sorted sets with Lua) for accuracy. Set tiered limits: per user (authenticated), per IP (anonymous), per endpoint (login stricter than general API). Always include X-RateLimit-* and Retry-After headers. Redis for distributed state. Monitor 429 rates to tune limits. Rate limiting is a security control — it prevents brute force, credential stuffing, and DDoS.
Quick recall
Sliding window (Redis Sorted Sets + Lua) for most use cases.
429 Too Many Requests + Retry-After header.
Tiered: per user, per IP, per endpoint. Stricter on login/password-reset.
Exclude health checks and metrics from rate limiting.
Redis for distributed state. In-memory fallback with degraded accuracy.