Why does this rate limiter block requests longer than the window?

Quelly
Mon, 06 Apr 2026 23:00:13 +0000

Hey everyone, I’m wiring up a simple per-user rate limiter for a small API and trying to keep it cheap in memory, but right now it feels too strict after a burst and users stay blocked longer than I expect.

class RateLimiter {
  constructor(limit, windowMs) {
    this.limit = limit;
    this.windowMs = windowMs;
    this.hits = new Map();
  }

  allow(userId, now = Date.now()) {
    const arr = this.hits.get(userId) || [];

    while (arr.length && now - arr[0] > this.windowMs) {
      arr.shift();
    }

    if (arr.length >= this.limit) {
      return false;
    }

    arr.push(now);
    this.hits.set(userId, arr);
    return true;
  }
}

Am I off by one on the window boundary here, or is this sliding-window approach the wrong tradeoff if I want bursts to recover exactly when the oldest hit expires?

Quelly

Read full topic