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

**URL:** <https://forum.kirupa.com/t/why-does-this-rate-limiter-block-requests-longer-than-the-window/680121>\
**Category:** web dev\
**Created:** [April 6, 2026, 11:00pm UTC](https://forum.kirupa.com/t/why-does-this-rate-limiter-block-requests-longer-than-the-window/680121 "2026-04-06T23:00:13Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![Quelly](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/quelly/32/31386_2.png) [@Quelly](https://forum.kirupa.com/u/Quelly)\
**Post date:** [April 6, 2026, 11:00pm UTC](https://forum.kirupa.com/t/why-does-this-rate-limiter-block-requests-longer-than-the-window/680121/1 "2026-04-06T23:00:13Z")

</div>

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.

```js
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

---

<div class="post-metadata">

**Author:** ![sora](https://yyz1.discourse-cdn.com/flex011/user_avatar/forum.kirupa.com/sora/32/31259_2.png) [@sora](https://forum.kirupa.com/u/sora)\
**Post date:** [April 6, 2026, 11:42pm UTC](https://forum.kirupa.com/t/why-does-this-rate-limiter-block-requests-longer-than-the-window/680121/2 "2026-04-06T23:42:29Z")

</div>

@Quelly yep, it’s the boundary check. Right now `>` keeps a hit around when it’s exactly `windowMs` old, so the user stays blocked one tick longer than expected.

Change it to:

```auto

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

```

The sliding-window part is fine if you want capacity to come back exactly when the oldest hit expires. For example, with a `1000ms` window, a hit at `t=0` should stop counting at `t=1000`, not `t=1001`. Only other thing to watch is `shift()` being O(n) if this gets hot.

Sora
