Advanced9 min readlive prototype

Sliding Window Log

Precise but memory-heavy: keep a log of every request timestamp.

Overview

What this concept solves

The sliding window log is the most precise rate-limiting algorithm in the canonical family. Instead of summarizing requests in counters, you store the timestamp of every single one. On each new request, you drop the timestamps older than your window, count what remains, and compare to the limit.

There is no approximation, no boundary problem, no smoothing artifact — the log knows exactly when each request happened. The cost is that the log grows with traffic, and at high request rates this becomes the most expensive of the five algorithms.

Mechanics

How it works

Store every timestamp

Per client, the state is a sorted set of timestamps. On each request:

  1. Remove all timestamps older than now − windowSize.
  2. If the remaining count is less than the limit, append now and allow.
  3. Otherwise, reject.

In Redis this is a ZSET keyed by client ID. The typical Lua-script pattern is ZREMRANGEBYSCORE (drop old entries), ZCARD (count what's left), and ZADD (insert the new timestamp) — all under a single atomic script.

Where the cost comes from

Memory grows linearly with request rate: at 1000 req/sec with a 60-second window, that's up to 60,000 timestamps stored per client. Each timestamp is ~16 bytes in Redis, so ~1 MB per client. Multiply by users and the cost is real.

Use it where precision matters

Sliding log shines for low-rate, high-value endpoints — login attempts, password resets, payment requests — where exact counting matters more than efficiency.

Interactive prototype

Run it. Break it. Tune it.

Sandboxed simulation embedded right in the page. No setup, no install.

About this simulation

Each click adds a timestamp to the log. The 8-second sliding window is the orange box on the right. Dots inside count; dots that have aged out are greyed and ignored. Burst a few times and watch entries decay leftward.

Hands-on

Try these on your own

Open the prototype above, run each experiment, predict the answer, then verify.

try 01

Watch entries decay

Send a single request. Watch the dot move leftward over 12 seconds — at 8 seconds, it crosses the dashed line and stops counting.

try 02

The smoothing property

Burst 8 requests. Five succeed. After 8 seconds (when the first entries age out), you'll be allowed again — and the timing is exact, unlike sliding window counter's approximation.

try 03

Stress-test it

Send a burst, then keep clicking once per second. You'll find the limiter rejects until exactly the right moment — never sooner, never later. That's the precision the log buys you.

In practice

When to use it — and what you give up

When to reach for it

  • Security-sensitive limits — login attempts, OTP requests, password resets, financial transactions.
  • Low request rates with strict ceilings — for example, '3 password resets per hour' is cheap to store and matters precisely.
  • Audit and forensics use cases where you also want the raw timestamps for analysis later.

Real-world example

Auth services typically use a sliding log (or its DB equivalent) for login throttling. The log is small (handful of timestamps per user), and the cost of being wrong — letting through a brute-force attempt — is large.

Pros

  • Exact: no approximation, no boundary issues, no weighting assumptions.
  • The log itself doubles as an audit trail — you know when each request happened.
  • Easy to reason about: 'X requests in the last Y seconds' is literal.

Cons

  • Memory scales with request volume — can be expensive at high rates.
  • Per-request cost is higher: must scan or delete a range of entries.
  • More moving parts in distributed setups (Lua scripts, ZSETs) than the simpler counter algorithms.

Reference

Code & further reading

A minimal reference implementation and pointers worth bookmarking.

sliding-log.ts
// Sliding log — exact, but stores every timestamp.
class SlidingLog {
  private log: number[] = [];

  constructor(
    private limit: number,
    private windowSizeMs: number,
  ) {}

  allow(): boolean {
    const now = Date.now();
    const cutoff = now - this.windowSizeMs;
    // Drop expired timestamps (log is append-ordered).
    while (this.log.length && this.log[0] <= cutoff) {
      this.log.shift();
    }
    if (this.log.length >= this.limit) return false;
    this.log.push(now);
    return true;
  }
}

// Redis (atomic via Lua):
//   ZREMRANGEBYSCORE rl:{user} -inf {now - window}
//   ZCARD rl:{user}              -> count
//   if count < limit:
//     ZADD rl:{user} {now} {uuid}
//   EXPIRE rl:{user} {window-seconds}

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 main reason sliding window log is rarely used at high request rates?

question 02 / 03

Where does sliding window log shine compared to the counter algorithms?

question 03 / 03

Which Redis primitives most naturally implement sliding window log?

0/3 answered

Was this concept helpful?

Tell us what worked, or what to improve. We read every note.