Beginner5 min readlive prototype

Fixed Window Counter

Count requests per discrete time window. Simple, but suffers boundary spikes.

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:

  1. On each request, check if the current window has expired (now - windowStart >= windowSize). If so, reset count = 0 and windowStart = now.
  2. If count < limit, increment count and allow the request.
  3. 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.

try 01

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.

try 02

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?

try 03

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: INCR plus EXPIRE. 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.

fixed-window.ts
// 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

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.