← Back to list

Why Fixed Window Rate Limiting Is Broken (And How Sliding Window Fixes It)

Most rate-limiting tutorials start with the same algorithm:

Nakshatrathange · 2026-06-15 07:24 · 0 claps · 4.7 min read paywalled
#technology #rate-limiting #fixed-windows #sliding-window-algorithm #rate-limiting-algorithms
Open on Medium ↗
Wiki topics: 💻 · Programming

Why Fixed Window Rate Limiting Is Broken (And How Sliding Window Fixes It)

Most rate-limiting tutorials start with the same algorithm:

  • Allow 100 requests per minute
  • Store a counter in Redis
  • Increment it on every request
  • Reset it every 60 seconds

Simple. Fast. Easy to implement. And surprisingly flawed.

Under real traffic conditions, a user can often send nearly twice the intended limit without violating the algorithm.

A “100 requests per minute” limiter can effectively become a “200 requests per minute” limiter.

This isn’t a bug in your implementation, It’s a weakness in the algorithm itself.

While building RedisWall, an open-source distributed rate limiter for Node.js and Redis, I implemented multiple rate-limiting strategies and quickly discovered why many production systems avoid fixed windows entirely.

Let’s see why.

How Fixed Window Rate Limiting Works

Fixed window rate limiting divides time into discrete buckets. For a limit of 100 requests per minute:

const windowId = Math.floor(Date.now() / 60000);
const key = `user:${userId}:${windowId}`;
const count = await redis.incr(key);
if (count === 1) {
  await redis.expire(key, 60);
}
return count <= 100;

Each minute gets its own Redis key:

user:123:28653451
user:123:28653452
user:123:28653453

When the minute changes, a new key is used and the counter starts from zero again.

The advantages are obvious:

  • Extremely simple
  • Constant memory usage
  • One Redis counter per user
  • Very fast

That’s why fixed windows are still one of the most common rate-limiting approaches. Unfortunately, they contain a loophole.

The Cross-Boundary Burst Problem

Imagine a limit of:

100 requests per minute

Now consider this traffic pattern:

00:59.900 → 100 requests
01:00.100 → 100 requests

Only 200 milliseconds passed.

But the requests landed in different windows.

Window A

00:00 → 00:59

Receives:

100 requests

Allowed.

Window B

01:00 → 01:59

Receives:

100 requests

Also allowed.

The rate limiter sees:

Window A = 100
Window B = 100

Both are within the limit. But reality looks very different. The server just received:

200 requests in 200 milliseconds

Even though the intended limit was:

100 requests per minute

The user effectively doubled the allowed rate simply by timing requests around a window boundary.

This is known as the cross-boundary burst problem.

Why This Actually Matters

At first glance this may seem harmless. Who cares if someone gets a few extra requests? The problem becomes serious when rate limiting exists to protect expensive resources.

Examples include:

  • Databases
  • Payment processors
  • AI APIs
  • Authentication systems
  • Third-party integrations
  • Webhook consumers

Imagine your infrastructure was sized for:

100 requests per minute

But a burst suddenly becomes:

200 requests in less than a second

That burst can:

  • Exhaust database connection pools
  • Increase latency
  • Trigger downstream rate limits
  • Cause cascading failures
  • Increase infrastructure costs

The closer your system operates to its limits, the more dangerous these bursts become.

The algorithm is enforcing the rule incorrectly.

The Core Problem

The issue isn’t Redis. The issue isn’t implementation quality. The issue is how fixed windows think about time.

Fixed windows treat time as a collection of separate buckets:

Minute 1
Minute 2
Minute 3
Minute 4

Once a bucket ends, the algorithm forgets everything that happened inside it. Requests that occurred one millisecond ago become completely irrelevant after a window reset. That’s the fundamental flaw.

How Sliding Window Rate Limiting Works

Sliding windows approach the problem differently.

Instead of asking: How many requests happened in this bucket?

They ask: How many requests happened during the last 60 seconds?

Time becomes continuous rather than segmented.

Suppose the current time is:

12:01:30

A sliding window simply looks back:

12:00:30 → 12:01:30

and counts every request that occurred during that period. No artificial boundaries exist. No reset moments exist. No bucket transitions exist. Only the last 60 seconds matter.

Implementing Sliding Windows with Redis

A common implementation uses Redis Sorted Sets. Each request timestamp becomes a member in the set.

const now = Date.now();
await redis.zremrangebyscore(
  key,
  0,
  now - windowMs
);
await redis.zadd(
  key,
  now,
  `${now}-${Math.random()}`
);
const count = await redis.zcard(key);
return count <= limit;

Here’s what happens:

Step 1: Remove Old Requests

zremrangebyscore(...)

Deletes timestamps older than the window.

Step 2: Record Current Request

zadd(...)

Stores the current timestamp.

Step 3: Count Active Requests

zcard(...)

Counts requests that still fall inside the active window.

The result is a continuously moving window of activity.

Why Sliding Windows Fix the Problem

Let’s revisit the same attack.

00:59.900 → 100 requests
01:00.100 → 100 requests

The sliding window doesn’t care about minute boundaries.

When processing the second burst, it looks back 60 seconds and sees:

200 requests

Not:

100 + 100

Simply:

200

If the limit is:

100 requests

Then:

200 > 100

The requests are blocked.

The exploit disappears entirely.

The Tradeoff

Nothing is free. Sliding windows improve accuracy, but they cost more.

Fixed Window

Pros:

  • Very fast
  • Constant memory
  • Simple implementation

Cons:

  • Cross-boundary bursts
  • Poor accuracy
  • Can exceed intended limits

Sliding Window

Pros:

  • Precise rate limiting
  • No burst exploit
  • Better protection for downstream services

Cons:

  • More Redis operations
  • Stores timestamps
  • Higher memory usage

In complexity terms: MemoryFixed Window O(1) Sliding WindowO(n)

Where n is the number of requests currently inside the active window.

Which One Should You Use?

For internal systems where occasional bursts are acceptable, fixed windows may be perfectly fine.

For public APIs, authentication endpoints, AI products, payment systems, and other resource-sensitive services, sliding windows are usually the better choice.

Most developers think rate limiting is about counting requests.

In reality, it’s about accurately modeling time.

Fixed windows simplify time into buckets.

Sliding windows model time continuously.

That small difference is what eliminates the exploit.

What I Built in RedisWall

While building RedisWall, I implemented both fixed window and sliding window strategies.

The fixed window strategy exists because it’s still useful when memory efficiency is the primary concern.

The sliding window strategy exists because many production APIs care more about correctness than a few additional Redis operations.

createRedisWall({
  redis,
  strategy: "sliding-window",
});

Choosing the algorithm becomes a configuration decision rather than an architectural rewrite.

Final Thoughts

Fixed window rate limiting isn’t broken because it crashes.

It’s broken because it doesn’t actually enforce the limit developers think they’re enforcing.

A “100 requests per minute” rule should mean exactly that.

With fixed windows, it often doesn’t.

Sliding windows solve the problem by treating time as a continuously moving interval rather than a collection of independent buckets.

The cost is slightly more memory and a few extra Redis operations.

For most modern APIs, that’s a tradeoff worth making.

In the next article, we’ll build a distributed rate limiter from scratch using Redis, Lua scripts, and Node.js, and explore how production systems handle rate limiting at scale.


메타데이터
post_id
6b16ba139025
slug
why-fixed-window-rate-limiting-is-broken-and-how-sliding-window-fixes-it-6b16ba139025
url
https://medium.com/@nakshatrathange/why-fixed-window-rate-limiting-is-broken-and-how-sliding-window-fixes-it-6b16ba139025
canonical_url
https://medium.com/@nakshatrathange/why-fixed-window-rate-limiting-is-broken-and-how-sliding-window-fixes-it-6b16ba139025
author_url
https://medium.com/@nakshatrathange
status
ok
fetched_at
2026-09-13 21:47:39