Rate limiting with sliding window in Redis - Time & Space Complexity
When using Redis to limit how often a user can do something, we want to know how the time to check and update limits changes as more requests happen.
We ask: How does the work grow when many requests come in quickly?
Analyze the time complexity of the following Redis commands for sliding window rate limiting.
-- Current timestamp in milliseconds
local current_time = redis.call('TIME')
local now = tonumber(current_time[1]) * 1000 + tonumber(current_time[2]) / 1000
local key = KEYS[1]
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
-- Remove old entries outside the window
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- Add current request timestamp
redis.call('ZADD', key, now, now)
-- Count requests in window
local count = redis.call('ZCOUNT', key, now - window, now)
return count <= limit
This code keeps timestamps of requests in a sorted set, removes old ones, adds the new one, and checks if the count is within the limit.
Look for repeated work that depends on input size.
- Primary operation: Removing old timestamps with
ZREMRANGEBYSCOREand counting withZCOUNT. - How many times: The removal scans all timestamps outside the window, which depends on how many old requests exist.
As more requests happen, the sorted set grows, but old entries get removed.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 removals and O(1) count |
| 100 | About 100 removals and O(1) count |
| 1000 | About 1000 removals and O(1) count |
Pattern observation: The work grows roughly with the number of stored timestamps, which depends on the request rate and window size.
Time Complexity: O(n)
This means the time to check and update the limit grows linearly with the number of requests stored in the window.
[X] Wrong: "The operations always take constant time regardless of request count."
[OK] Correct: Removing old entries and counting depend on how many timestamps are stored, so more requests mean more work.
Understanding how Redis commands scale with data size helps you design efficient rate limiting, a common real-world problem.
What if we used a fixed-size queue instead of a sorted set? How would the time complexity change?
Practice
Solution
Step 1: Understand sliding window concept
Sliding window rate limiting counts actions in a recent time frame that moves forward as time passes.Step 2: Identify purpose in Redis context
Redis uses sorted sets to track timestamps of actions to enforce limits fairly over time.Final Answer:
To count actions within a moving time frame for fair limits -> Option AQuick Check:
Sliding window = moving time frame count [OK]
- Thinking sliding window blocks permanently
- Confusing with password storage
- Assuming it sorts data alphabetically
Solution
Step 1: Identify command to add to sorted set
ZADD adds members with scores (timestamps) to a sorted set in Redis.Step 2: Confirm other commands' purpose
GET retrieves string values, HSET sets hash fields, LPUSH adds to lists, none add to sorted sets.Final Answer:
ZADD -> Option AQuick Check:
ZADD adds to sorted set [OK]
- Using GET which only reads strings
- Confusing HSET (hashes) with sorted sets
- Using LPUSH which is for lists
local now = tonumber(redis.call('TIME')[1])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now)
return 1
else
return 0
endWhat will be the return value if the current count is equal to the limit?
Solution
Step 1: Analyze the condition count < limit
If count is equal to limit, the condition count < limit is false.Step 2: Determine return value when condition is false
When false, the script returns 0, indicating rate limit reached.Final Answer:
0 -> Option CQuick Check:
count == limit returns 0 [OK]
- Assuming count == limit returns 1
- Expecting an error instead of 0
- Thinking nil is returned
local now = tonumber(redis.call('TIME')[1])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count <= limit then
redis.call('ZADD', key, now, now)
return 1
else
return 0
endWhat is the bug in this script?
Solution
Step 1: Check condition count <= limit
This condition allows adding a request when count equals limit, exceeding limit by one.Step 2: Verify other commands usage
ZREMRANGEBYSCORE usage is correct to remove old timestamps; ZADD accepts numbers as members; TIME returns array with string seconds converted to number.Final Answer:
Using <= allows one extra request beyond limit -> Option BQuick Check:
count <= limit allows limit+1 requests [OK]
- Thinking ZREMRANGEBYSCORE is wrong here
- Believing ZADD can't use numbers as members
- Confusing TIME return type
Solution
Step 1: Understand sliding window enforcement
Use sorted sets to store timestamps, remove old ones, count current requests, and add new timestamp.Step 2: Evaluate each option's commands
ZREMRANGEBYSCORE to remove old timestamps, ZCARD to count, ZADD to add current timestamp uses ZREMRANGEBYSCORE, ZCARD, ZADD correctly for sliding window. LPUSH to add timestamp, LLEN to count, LTRIM to remove old timestamps uses lists which are less efficient for range removals. HSET to store count, GET to read count, INCR to increment count uses hashes and counters, not suitable for sliding window. SETNX to set a flag, EXPIRE to reset after 60 seconds uses simple flags, not counting requests.Final Answer:
ZREMRANGEBYSCORE, ZCARD, ZADD sequence -> Option DQuick Check:
Sorted sets + remove old + count + add = correct [OK]
- Using lists which are inefficient for sliding windows
- Using counters without timestamps
- Using flags without counting requests
Ready to prove it?
Pass the test and this pattern moves unknown
