Software Engineering·July 21, 2026·4 min read

Rate Limiting and Throttling

Rate limiting caps how many requests a client can make in a given window. It protects against abuse, runaway costs from paid APIs, and accidental self-inflicted overload.

rate-limitingthrottlingsecurityscalingengineering

Rate limiting caps how many requests a client can make in a given time window. It's a small piece of infrastructure that does a lot of work — protecting you from abuse, preventing runaway costs on paid APIs, and ensuring one heavy user doesn't degrade the experience for everyone else.

If Cartara flagged this in your diff, you likely added rate-limiting middleware, a Redis counter, or headers like 429 Too Many Requests.


What It Protects Against

Abuse and attacks — brute-forcing login forms, scraping your data, flooding your API with requests.

Runaway costs — a misbehaving script hammering an endpoint that calls OpenAI, Twilio, or another paid API can run up a huge bill in minutes. Rate limits are your financial backstop.

Accidental overload — a client retrying aggressively after an error can cascade into an outage. A rate limit stops the stampede.

Fair usage — preventing one user from consuming resources that degrade the experience for others.


The Core Algorithms

Token Bucket

Each client has a bucket that refills with tokens at a steady rate. Each request spends a token; if the bucket is empty, the request is rejected. Allows bursts (you can spend a full bucket at once) while capping the long-run average.

This is the most popular general-purpose algorithm and the best default for most APIs.

Sliding Window

Tracks requests over a rolling time window rather than fixed clock intervals. More accurate than a fixed window (which has an edge case where a client can double their limit right around the window boundary).

Fixed Window

Count requests per fixed interval (e.g. 100 per minute, resetting on the minute). Simple but has the edge problem described above.

Practical default: token bucket for general API limiting; sliding window when accuracy matters.


What to Limit By

Per user / API key — the standard for authenticated APIs. Ties usage to an account and supports tiered plans.

Per IP address — for unauthenticated endpoints. Note: many users can share one IP (offices, mobile carriers), so this is a blunt instrument. Attackers also rotate IPs.

Per endpoint — protect expensive operations more tightly than cheap ones. Login endpoints especially should have strict limits to prevent brute-force attacks.

Global — a backstop ceiling on total traffic to protect the whole system.

You often layer several: a generous per-user limit plus a strict per-endpoint limit on the sensitive or expensive routes.


What You'll See in Your Code

Rate limiting typically appears in diffs as middleware or a library like express-rate-limit:

```ts
import rateLimit from 'express-rate-limit';

// Strict limit for login — prevent brute force
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // 10 attempts per window
message: { error: 'Too many login attempts, please try again later' },
});

app.post('/auth/login', loginLimiter, handleLogin);

// Looser limit for general API use
const apiLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100,
standardHeaders: true,
legacyHeaders: false,
});

app.use('/api', apiLimiter);
```

Redis-based limiting for distributed setups (multiple app servers):

```ts
import { rateLimit } from 'express-rate-limit';
import { RedisStore } from 'rate-limit-redis';

const limiter = rateLimit({
store: new RedisStore({ client: redis }),
windowMs: 60_000,
max: 100,
});
```

Response headers that tell clients about their limit state:

```
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 1719878460
Retry-After: 60
```


Distributed Rate Limiting

If you run multiple app servers, an in-memory counter on each one doesn't work — a client can hit 5 servers and get 5× the intended limit.

The standard fix is a shared counter in Redis. Every server checks and increments the same Redis key, so the limit is enforced globally. Upstash Redis (serverless, generous free tier) is the easiest option for small apps.


Good Practices

Always return 429 with a Retry-After header so clients know how long to wait before retrying — not just a generic error.

Rate-limit login and AI/paid endpoints first. These are the routes where abuse causes the most damage.

Set spend alerts on paid APIs as a financial backstop, in case a limit is misconfigured or bypassed.

Don't build from scratch. Most frameworks have rate-limiting middleware, and platforms like Cloudflare can enforce limits at the edge before traffic reaches your servers.


Related concepts

Scalability Patterns
Scalability is a system's ability to handle growing load without a rewrite. This guide explains vertical vs horizontal scaling, statelessness, caching, queues, and when to reach for each pattern.
Secrets and Config Management
Every app has settings that change between environments and sensitive values that must never leak. This guide explains the difference between config and secrets, where each should live, and how to manage rotation.
System Design Basics
System design is how the individual parts of your app — database, cache, queue, and APIs — fit together into something that's fast, reliable, and able to grow. This guide covers the core building blocks and the decisions that shape every system.

Turn shipping into understanding

Cartara measures what your team actually learns from every AI coding session.

Join the waitlist