Skip to content

Rate Limiting Explained — Complete Beginner's Guide

DodaTech Updated 2026-06-28 5 min read

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:

  1. Brute force attacks — Attackers trying thousands of passwords
  2. Denial of service — Malicious clients overwhelming your server
  3. Cost control — APIs with usage-based pricing need to limit consumption
  4. 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

  1. Rate limiting by IP only — Users behind a corporate NAT share one IP. Rate limit by user ID when authentication is available.

  2. Not returning Retry-After headers — Clients need to know when to retry. Always include timing information in 429 responses.

  3. Using in-memory stores in multi-server deployments — Each server has its own count, making rate limiting ineffective. Use Redis.

  4. Not distinguishing between different endpoints — Login attempts need stricter limits than read-only GET requests.

  5. Forgetting to clean up old data — In-memory stores grow indefinitely without cleanup. Use TTL-based approaches.

Practice Questions

  1. What does HTTP status code 429 mean? Too Many Requests. The client has exceeded the rate limit and should back off.

  2. 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.

  3. 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.

  4. 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

What HTTP headers are used for rate limiting?

X-RateLimit-Limit (max requests per window), X-RateLimit-Remaining (remaining requests), and Retry-After (seconds until reset).

Can rate limiting be bypassed?

Distributed attacks from many IPs can bypass IP-based limiting. Combine with user-based limiting and CAPTCHA for stronger protection.

What happens when rate limit is exceeded?

The server returns 429 Too Many Requests. The client should wait for the Retry-After duration before retrying.

Should rate limits apply to all endpoints equally?

No. Login, registration, and password reset endpoints need stricter limits than read-only endpoints.

How do I choose the right rate limit values?

Start with conservative limits (100 req/min for authenticated users). Monitor actual usage and adjust based on data.

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