Skip to main content
The Fixed Window algorithm divides time into discrete buckets based on floor(epochSeconds / windowSize). All requests in the same bucket share a single Redis STRING key. This is the simpler of the two algorithms, offering low memory use at the cost of potential burst behavior at window boundaries.

Configuration

rate-limiter:
  use-algo: fixed-window-algo
  window-size: 60      # seconds per window
  max-requests: 5      # max requests per window

Redis key format

The algorithm uses a single STRING key per user per window:
ComponentValue
Key patternrlaas:rate_limit:{userId}:{window}
{window}Math.floor(epochSeconds / windowSize)
Data typeSTRING (integer counter)
TTLSet to windowSize seconds on the first request in each window
Example: for userId=alice, windowSize=60, and Unix time 1700000100:
  • window = floor(1700000100 / 60) = 28333335
  • Key = rlaas:rate_limit:alice:28333335

Lua script

The full fixedWindowSize.lua script executed atomically in Redis:
local windowSize = tonumber(ARGV[1])      -- e.g. 60 (seconds)
local limit = tonumber(ARGV[2])           -- e.g. 5 (max requests)

-- Get current server time from Redis (avoids client clock skew)
local now = redis.call('TIME')
local currentSeconds = tonumber(now[1])

-- Compute which window bucket this request falls into
local window = math.floor(currentSeconds / windowSize)

-- Build the full Redis key including the window bucket
local key = KEYS[1] .. ':' .. window

-- Atomically increment the counter for this window
local current = redis.call('INCR', key)

-- Set TTL only on the first request so the key expires at window end
if current == 1 then
  redis.call('EXPIRE', key, windowSize)
end

-- Read remaining TTL for the response
local ttl = redis.call('TTL', key)

-- Return {allowed, currentCount, ttl}
if current > limit then
  return {0, current, ttl}   -- denied
else
  return {1, current, ttl}   -- allowed
end

Response fields

FieldTypeDescription
allowedbooleantrue if under limit, false if exceeded
currentCountlongTotal requests in current window (including this one if allowed)
ttllongSeconds until the current window expires

Boundary burst behavior

Because each window is an independent counter, a user can send the maximum number of requests at the very end of one window and again at the very start of the next — and all of them will be allowed. Example with max-requests: 5 and window-size: 60:
Time (s)WindowRequestsOutcome
59N5All 5 allowed (window N count = 5)
61N+15All 5 allowed (window N+1 count = 5)
Within a two-second span, 10 requests pass — double the configured limit. This is an inherent property of fixed-window counting.
Use sliding-window-algo if you need to prevent boundary bursts. The sliding window tracks every request individually and enforces the limit over a true rolling time range.

See also

Sliding Window algorithm reference

Compare the sliding window approach: true rolling window, no boundary bursts, higher Redis memory use.

Build docs developers (and LLMs) love