Overview
What this concept solves
The fixed window counter is the simplest rate limiter that anyone ever writes. Pick a time window (say, 1 minute). Count requests in that window. If the count exceeds the limit, reject. When the window expires, reset the counter to zero. That's the whole algorithm.
Its simplicity is also its main weakness: by treating the window as a hard boundary, it allows up to 2× the limit in a tight enough time span — by stacking bursts on either side of the reset. The prototype above demonstrates this clearly.
Mechanics
How it works
One counter, one timer
The state per client is a tuple: (count, windowStart). The algorithm:
- On each request, check if the current window has expired (
now - windowStart >= windowSize). If so, resetcount = 0andwindowStart = now. - If
count < limit, incrementcountand allow the request. - Otherwise, reject.
In production this is usually implemented in Redis as INCR rl:{client}:{window-id} with an EXPIRE — two commands, atomic, distributed.
The boundary problem
Suppose your limit is 5 requests per 10-second window. A client sends 5 requests in the last second of one window, then 5 more in the first second of the next. That's 10 requests in ~2 seconds — twice the intended rate. The algorithm never noticed, because each window saw exactly 5.
Why this matters
A determined attacker can sustain 2× your nominal limit indefinitely by syncing bursts to window boundaries. For most usage this isn't catastrophic, but if your downstream truly cannot handle 2× capacity, fixed window is the wrong tool.
Interactive prototype
Run it. Break it. Tune it.
Sandboxed simulation embedded right in the page. No setup, no install.
About this simulation
A counter resets every 10 seconds. Up to 5 requests are allowed per window. Watch the recent-windows strip below — and try clicking 'Burst of 8' right before the window resets to see the classic boundary problem.
Hands-on
Try these on your own
Open the prototype above, run each experiment, predict the answer, then verify.
Trigger the boundary attack
Wait until the timer shows ~0.5s remaining. Click 'Send request' 5 times fast. As the window resets, immediately send 5 more. You just sent 10 requests in ~1.5 seconds against a 5-per-10s limit.
Read the history strip
The bars below show the last few windows. A red bar means the limit was hit in that window. Are your burst patterns even, or do they cluster?
Predict the next reset
Look at the time-remaining label. If you send a burst now, how many will succeed in the current window? How many will need to wait? Estimate before clicking — then verify.
In practice
When to use it — and what you give up
When to reach for it
- Internal services where rough-and-ready throttling is enough and exact behavior at boundaries doesn't matter.
- Cost-quota systems that bill per calendar interval (e.g. "1000 requests per hour") — the window boundary maps naturally to the billing boundary.
- When operational simplicity is more important than perfect smoothing — debugging is trivial because each window's count is a single integer.
Real-world example
GitHub's REST API uses a fixed window: 5,000 requests per hour, with X-RateLimit-Reset telling you the exact UNIX timestamp when the next window starts. You can plan around it.
Pros
- Dead-simple to implement and reason about — one integer per client per window.
- Trivial in Redis:
INCRplusEXPIRE. O(1) memory and O(1) per request. - Window boundaries align with human-readable units (top of the minute, top of the hour) — easy to communicate to API consumers.
Cons
- The boundary problem: up to 2× the limit can pass in a short interval straddling the reset.
- Coarse-grained — no information about distribution within the window.
- Resets cause sudden behavior changes (everyone's quota refills at the same instant).
Reference
Code & further reading
A minimal reference implementation and pointers worth bookmarking.
// In-memory fixed window. Production uses Redis INCR + EXPIRE.
class FixedWindow {
private windowStart = Date.now();
private count = 0;
constructor(
private limit: number,
private windowSizeMs: number,
) {}
allow(): boolean {
const now = Date.now();
if (now - this.windowStart >= this.windowSizeMs) {
this.windowStart = now;
this.count = 0;
}
if (this.count >= this.limit) return false;
this.count++;
return true;
}
}
// Redis equivalent (atomic, distributed)
// const key = `rl:${userId}:${Math.floor(Date.now() / windowMs)}`;
// const count = await redis.incr(key);
// if (count === 1) await redis.expire(key, windowSeconds);
// return count <= limit;References & further reading
3 sources- Docsdocs.github.com
GitHub Docs — Rate limits for the REST API
Canonical example of fixed-window quotas in production: 5,000 req/hour with
X-RateLimit-Reset. - Articleblog.cloudflare.com
Cloudflare — How we built rate limiting capable of scaling to millions of domains
Compares fixed window, sliding window, and the boundary problem with real numbers.
- Articlegithub.blog
GitHub Engineering — How we scaled the GitHub API with a sharded, replicated rate limiter in Redis
How the simple
INCR+EXPIREcounter is made distributed and resilient at scale.
Knowledge check
Did the prototype land?
Quick questions, answers revealed on submit. No scoring saved.
question 01 / 03
What is the 'boundary problem' in fixed-window rate limiting?
question 02 / 03
Which property makes fixed window appealing despite the boundary problem?
question 03 / 03
Fixed window with limit 100 per minute. A client sends 100 requests at 12:00:59.9 and 100 more at 12:01:00.1. What happens?
0/3 answered
Was this concept helpful?
Tell us what worked, or what to improve. We read every note.