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:
- Remove all timestamps older than
now − windowSize. - If the remaining count is less than the limit, append
nowand allow. - 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.
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.
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.
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 — 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- Docsredis.io
Redis — Build rate limiters: fixed window, sliding window log, and more
Canonical
ZREMRANGEBYSCORE+ZCARD+ZADDLua script for the sorted-set sliding log. - Articlestripe.com
Stripe Engineering — Scaling your API with rate limiters
Discusses sliding log alongside token bucket trade-offs in production.
- Articleen.wikipedia.org
Wikipedia — Generic cell rate algorithm (GCRA)
The dense, log-free alternative for high-rate scenarios — exact, but O(1) memory.
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.