A rate limit bounds how often a caller may perform an operation during a window. It protects shared capacity and discourages automated abuse, but does not replace authentication, authorization, or input limits. Different routes have different costs: a search may be cheap while a report export is expensive. Choose a key that reflects the real caller, then account for proxies and shared clients; raw IP address alone can punish a whole office. The server should return a clear limit response and a retry hint when appropriate. Limits must work across all serving instances or a caller can multiply capacity by reaching several workers.
Rate Limits and Request Budgets
Working case
A reviewer can open case pages freely but may request only three large exports per minute. The fourth export receives 429 before the database begins the expensive report. A legitimate retry after the window can proceed. The in-memory function below models one process for a test; a production fleet needs a shared counter or service-level limiter. The response should not expose internal account details. Also cap body size and execution time so one allowed request cannot consume unlimited resources.
Implementation
const requestWindows = new Map();
function allowExport(reviewerId, nowMs) {
const windowStart = Math.floor(nowMs / 60000) * 60000;
const current = requestWindows.get(reviewerId);
const used = current?.windowStart === windowStart ? current.used : 0;
if (used >= 3) return { status: 429 };
requestWindows.set(reviewerId, { windowStart, used: used + 1 });
return { status: 200 };
}
for (let attempt = 0; attempt < 4; attempt++) {
console.log(allowExport(29, 120000).status);
}Observed output
200
200
200
429Cost and boundaries
A map lookup and counter update are expected O(1), with O(C) state for C active caller keys. A distributed limiter adds a network round trip and must define behavior when its store is unavailable. Fixed windows can allow bursts at boundaries; token buckets or sliding windows smooth that behavior at greater implementation cost. Decide whether to fail closed or degrade for a critical route. Monitor rejected requests by route and caller class without storing sensitive payloads.
Common Mistakes
- Do not use rate limiting as a replacement for permission checks.
- Do not keep independent limits on each worker in a scaled service.
- Do not use one limit for cheap reads and costly exports.
Connected lessons
Backend and API Systems; Cursor Pagination for Changing Collections; Idempotent Write Requests and Lost Responses; Background Jobs and the Outbox Boundary; HTTP requests: keep method, status, and body contracts separate; Form submission: validate on the server and return field errors; Routing: validate path parameters and return a stable error shape.
Failure trace
An office of 60 reviewers shares one gateway address. An IP-only export limit lets one busy reviewer exhaust the budget for everyone; a second failure appears when four app workers each permit three exports, multiplying the intended allowance. Use an authenticated subject and route cost as the key where possible, coordinate counters across instances, and cap the work of a single accepted request.
Verification
- Issue the same caller's requests through two workers and confirm the shared limit stays fixed.
- Send requests from two users behind one address and check that one does not consume the other's allocation.
- Submit one oversized allowed export and verify body, query, and execution budgets still apply.
Decision note
A rejected response should state when a retry can make sense, without disclosing another account's usage. Observe rejection rates by endpoint and caller class before changing limits.
Apply and check
Build Project: paginated inspection feed with safe writes; then check the boundary with Web Development: data and API contracts quiz.
Further connections
Abuse-Resistant Public Endpoints; Expensive Request Work Budgets and Load Shedding.
