ENGINEERING NOTE

What a sliding-window rate limiter actually counts

I used Upstash’s slidingWindow in ThinkBoard without knowing how it worked. Reading its Lua script showed it is an estimate, and why that is a good trade.

ThinkBoard limits its API to 50 requests per 60 seconds with one line: Ratelimit.slidingWindow(50, "60 s"). When I wrote it, “sliding window” just sounded better than “fixed window”. This note is what I found when I read the script that @upstash/ratelimit runs inside Redis.

The problem with a fixed window

A fixed window counts requests per clock minute and resets at :00. A client can send 50 requests at 0:59 and 50 more at 1:00. Both minutes look fine, but the server took 100 requests in about two seconds.

What Upstash does instead

A true sliding window would store a timestamp for every request and count those in the last 60 seconds. Upstash avoids that. It keeps just two counters, one for the current clock window and one for the previous one, and estimates the sliding count by assuming the previous window’s requests were spread evenly.

@upstash/ratelimit: slidingWindow (Lua, simplified)
local percentageInCurrent = ( now % window ) / window
requestsInPreviousWindow =
  math.floor(( 1 - percentageInCurrent ) * requestsInPreviousWindow)

if requestsInPreviousWindow + requestsInCurrentWindow >= limit then
  return -1                                   -- rejected, nothing is counted
end

redis.call("INCRBY", currentKey, 1)           -- allowed, count it

Fifteen seconds into a window, a quarter of the current window has passed, so three quarters of the previous window still counts. With 40 requests last window and 10 so far this window, the estimate is floor(0.75 × 40) + 10 = 40, so ten more are allowed.

Why the estimate is a good trade

  • Memory is two integers per key, no matter how many requests arrive.
  • The whole check runs as one Lua script, so Redis executes it atomically. There is no read-then-write gap like the one in SkillMatch’s attend route.
  • Rejected requests are not counted, so a client that keeps retrying is not punished forever.
  • The cost is precision: if traffic in the previous window was bunched up rather than even, the estimate is off. For protecting a free-tier API that is fine.

What it taught me about my own code

The algorithm was never ThinkBoard’s weak point. The key was. I pass the fixed string "my-rate-limit", so every visitor shares one budget of 50. The case study page has a simulation of this exact script where you can watch one visitor’s burst lock out another, then switch to one key per visitor and see the fix.