Rate Limiting Explained — Complete Beginner's Guide
In this tutorial, you will learn about Rate Limiting Explained. We cover key concepts, practical examples, and best practices to help you master this topic.
Rate limiting controls how many requests a client can make to an API within a specific time window, protecting backend services from abuse and ensuring fair resource allocation across all users.
What You'll Learn
By the end of this tutorial, you will understand what rate limiting is, why it matters, the main algorithms used, and how to choose the right Strategy for your application.
Why It Matters
Without rate limiting, a single client can overwhelm your server, causing downtime for legitimate users. DodaTech's production APIs use rate limiting to prevent brute force attacks and ensure service availability.
Real-World Use
Doda Browser's bookmark sync API allows 100 requests per minute for sync operations and 5 requests per minute for login attempts, protecting user data from automated attacks.
Rate Limiting Learning Path
flowchart LR
A[Middleware Patterns] --> B[Rate Limiting]
B --> C[Algorithms]
C --> D[Implementation]
B --> E{You Are Here}
style E fill:#f90,color:#fff
Understanding Rate Limiting
Think of rate limiting like a busy coffee shop. The barista can only make 10 drinks per minute. When more customers arrive, they wait in line. If the line gets too long, new customers are asked to come back later.
How Rate Limiting Works
Every request to your API is tracked by some identifier (IP address, user ID, or API key). The rate limiter checks how many requests this identifier has made in the current time window.
const requestCounts = new Map();
function simpleRateLimiter(req, res, next) {
const clientIp = req.ip;
const now = Date.now();
const windowMs = 60000;
const maxRequests = 10;
if (!requestCounts.has(clientIp)) {
requestCounts.set(clientIp, []);
}
const timestamps = requestCounts.get(clientIp);
const recent = timestamps.filter(t => now - t < windowMs);
if (recent.length >= maxRequests) {
return res.status(429).json({ error: "Too many requests" });
}
recent.push(now);
requestCounts.set(clientIp, recent);
next();
}
app.use(simpleRateLimiter);
Expected behavior: After 10 requests in 60 seconds, the client receives a 429 status code.
Why Rate Limiting Matters
Rate limiting prevents several categories of problems:
- Brute force attacks — Attackers trying thousands of passwords
- Denial of service — Malicious clients overwhelming your server
- Cost control — APIs with usage-based pricing need to limit consumption
- Fairness — Preventing noisy neighbors from consuming all resources
Rate Limiting in Distributed Systems
In a single-server setup, rate limiting is straightforward: track counts in memory. In distributed systems with multiple servers, you need a shared store like Redis.
const express = require("express");
const Redis = require("ioredis");
const redis = new Redis();
async function distributedRateLimiter(req, res, next) {
const key = `ratelimit:${req.ip}`;
const current = await redis.incr(key);
if (current === 1) {
await redis.expire(key, 60);
}
if (current > 10) {
return res.status(429).json({ error: "Too many requests" });
}
next();
}
Common Mistakes
Rate limiting by IP only — Users behind a corporate NAT share one IP. Rate limit by user ID when authentication is available.
Not returning Retry-After headers — Clients need to know when to retry. Always include timing information in 429 responses.
Using in-memory stores in multi-server deployments — Each server has its own count, making rate limiting ineffective. Use Redis.
Not distinguishing between different endpoints — Login attempts need stricter limits than read-only GET requests.
Forgetting to clean up old data — In-memory stores grow indefinitely without cleanup. Use TTL-based approaches.
Practice Questions
What does HTTP status code 429 mean? Too Many Requests. The client has exceeded the rate limit and should back off.
Why is IP-based rate limiting problematic for corporations? All employees behind a corporate NAT share the same public IP, so one user's behavior affects everyone.
What is the difference between rate limiting and throttling? Rate limiting blocks requests exceeding a threshold. Throttling slows down requests but does not block them.
Challenge: Implement a rate limiter that gives authenticated users higher limits than anonymous users.
function tieredRateLimiter(req, res, next) {
const limit = req.user ? 100 : 10;
const key = req.user ? `user:${req.user.id}` : `ip:${req.ip}`;
// Check and enforce limit
}
FAQ
Mini Project
Build a basic rate limiting system with tiered limits (authenticated vs anonymous), proper headers, and clean error messages.
const express = require("express");
const app = express();
const store = new Map();
function tieredLimiter(req, res, next) {
const key = req.headers.authorization ? `user:${req.headers.authorization}` : `ip:${req.ip}`;
const limit = req.headers.authorization ? 100 : 10;
const windowMs = 60000;
const now = Date.now();
if (!store.has(key)) store.set(key, []);
const timestamps = store.get(key).filter(t => now - t < windowMs);
if (timestamps.length >= limit) {
res.setHeader("Retry-After", Math.ceil(windowMs / 1000));
return res.status(429).json({
error: "Too many requests",
limit,
remaining: 0,
resetAt: new Date(now + windowMs).toISOString()
});
}
timestamps.push(now);
store.set(key, timestamps);
res.setHeader("X-RateLimit-Limit", limit);
res.setHeader("X-RateLimit-Remaining", limit - timestamps.length);
next();
}
app.use(tieredLimiter);
app.get("/api/data", (req, res) => {
res.json({ data: "protected" });
});
app.listen(3000);
What's Next
Now that you understand rate limiting basics, explore the different algorithms used for rate limiting. Then learn about implementing the token bucket algorithm.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro