Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
The admin:
CRUD operations for rate limit rules. POST /ratelimit/rules creates a new rule. GET /ratelimit/rules lists all rules with filtering. , another ger for GET /ratelimit/rules/ :id PUT /ratelimit/rules/:id updates a rule. DELETE /ratelimit/rules/:id removes a rule.
This API hits PostgreSQL directly. It is called by operations teams, not by production traffic. Latency of 100ms is acceptable.
uesr:
call some service--> hit API rate limiter--> limiter decide if user extends a certain threshold--> allow pass through or not
Inside the limiter there is the deicsion API, which is a POST request to see
Decision API:
The core endpoint: POST /ratelimit/check. The gateway sends {"user_id": "u_42", "endpoint": "/api/messages", "scope": "user"}. On allow, the response is {"allowed": true, "remaining": 14, "limit": 100, "reset": 1710432000}. On denial: {"allowed": false, "remaining": 0, "limit": 100, "retry_after": 23}. The response takes under 1ms because it reads from Redis, never PostgreSQL.
The response:
the response includes the rateLimit, remainingLimit, and when the limit got reset
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
The request path: Client sends a request to the API Gateway. The gateway extracts the user identity and forwards the request to the Rate Limiting Service (RLS). RLS constructs a Redis key from the user identity and endpoint, runs a Lua script on Redis to atomically check and decrement tokens, and returns allow or deny. If allowed, the gateway forwards the request to the Backend Service. If denied, the gateway returns 429 with Retry-After.
The Lua script executes these steps atomically on Redis:
tokens and last_refill from the bucket key.elapsed = now - last_refill.tokens = min(max_tokens, tokens + elapsed * refill_rate).tokens >= 1, decrement by 1 and return allowed with remaining count. Otherwise return denied with seconds until next token arrives.tokens and last_refill back to the hash. Set TTL to 2x the window.This lazy refill approach means idle buckets consume zero Redis resources. No background timer needed. Tokens accumulate mathematically when the next request arrives.
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
DATABASE DESIGN:
A postgre database on ruleID. with rule_id (uuid), scope type(storing a enum of user, org, ip, apiKey.etc), scope_val(string value), api Endpoint, max_request, algorithm applied (slidinng window, token bucket), window_second(if using sliding window), burst_size (for token bucket). The 2 columns are needed because the algo applied is not sure]
Each rate limit bucket is a Redis hash. For token bucket, the hash stores two fields: tokens (current count as a float) and last_refill (Unix timestamp in milliseconds). For sliding window counter, the hash stores prev_count (previous window total) and curr_count (current window total). Each key has a TTL of 2x the window duration, sliding window needs data from both current and previous windows, so 1x TTL would expire previous-window data too early
redis key schema:
Redis keys follow the pattern rl:{scope_type}:{scope_value}:{api_endpoint}. For example, rl:user:u_42:/api/search or rl:ip:10.0.0.1:/api/login. The scope type prefix enables efficient scanning of all limits for a given scope, and the compound key ensures that different scope-endpoint combinations never collide.