Rate limiting PRs look small and often aren't. A one-line change to a key, a window type, or a storage backend can open abuse vectors, break API clients, or silently remove per-user ceilings across every endpoint that imports the middleware. Reviewing one well takes about 30 to 45 minutes if you work through the right checklist in order.
You've probably seen a PR like this: the ticket says "increase limit for paid tier," the diff is 20 lines, and the tests pass. The problem shows up a week later when a single IP hammers the endpoint because the key switched from user ID to IP address and nobody caught it. That's the scenario these steps are designed to prevent.
1. Understand What Rate Limiting Code Actually Controls
Rate limiting enforces a ceiling on how often a caller can trigger an action within a time window. Any change to that logic shifts who gets blocked, who doesn't, and when. Fix three things in your mind before you read a single line of the diff: the counting unit (requests, tokens, or events), the window type (fixed, sliding, or token bucket), and the storage backend (in-memory, Redis, or database).
Rate limiting typically sits in middleware, a decorator, or a shared service layer. A one-line change there affects every endpoint that imports it. Knowing the architecture upfront stops you from approving a change that looks trivial but carries broad consequences.
2. Read the Ticket and the Tests Before the Diff
The ticket tells you the intended behaviour. The tests tell you what the author thought they proved. The diff tells you what actually changed. Read them in that order.
Check the ticket for the reason the limit is changing: a capacity increase, a bug fix, and a new paid-tier threshold each carry different risk profiles. A capacity increase might be fine. A bug fix might reveal the old logic was wrong everywhere.
Scan the test file before the implementation. If the tests only cover the happy path (a user under the limit), flag that before commenting on anything else. Missing edge-case tests are the most common gap in rate limiting PRs. The cases to look for: exactly-at-limit requests, one-over-the-limit requests, clock-boundary requests at the last millisecond of a window, and concurrent requests arriving simultaneously.
When tests are thin, leave a specific comment rather than blocking the PR outright. "Can you add a test for two requests arriving in the same millisecond at window rollover?" is more useful than a vague request for better coverage.
3. Audit the Key, the Window, and the Store
These three variables define the security and correctness of any rate limiter. Each one is a place the diff can introduce a regression.
3a. Check the rate limit key
The key is what the limiter counts against, and a wrong key is a security hole. Confirm the key is scoped to the right identity: authenticated user ID for logged-in flows, IP address only as a last resort for anonymous ones. A PR that switches from user ID to IP silently allows one user to hit the limit from multiple IPs, which is almost always a downgrade.
3b. Check the window logic
A fixed window resets at a clock boundary. That means a burst of requests at the end of one window and the start of the next can double the effective rate, a well-known property of fixed windows. A sliding window avoids this. If the diff changes window type, ask why and confirm the product team accepted the tradeoff explicitly.
3c. Check the storage backend
In-memory counters don't survive a process restart and don't work across multiple server instances. If the app runs more than one process, the store must be shared, typically Redis. A diff that moves from Redis to in-memory is almost always a bug. Treat it that way until the author proves otherwise.
4. Look for Missing or Broken Error Responses
A rate limiter that blocks requests silently is harder to debug and breaks API contracts. The diff should include a correct 429 status and a Retry-After header.
HTTP 429 is the standard status for rate limit exceeded. Some codebases return 503 or 403 by mistake. If the diff changes the response code, flag it with a reference to the standard. The Retry-After header tells the client when to try again. Without it, well-behaved clients back off with exponential jitter and poorly written ones hammer the endpoint immediately.
Check that the error body includes a machine-readable reason code, not just a human-readable string. Clients that parse error bodies need a stable key to branch on. Also confirm the limiter doesn't expose internal detail in the error: the Redis key schema, the counter value, or the exact algorithm are information an attacker can use to probe the system.
5. Check the Bypass and Admin Paths
Most rate limiters have an allowlist or an admin bypass. Changes to those paths carry the highest risk in the entire PR.
Look for any condition that skips the limiter entirely: an environment variable, a feature flag, a role check, or a hardcoded IP range. Confirm each one is intentional and documented in the ticket or a code comment. A common mistake is a bypass added for testing that ships to production because it checked an environment variable that also exists in the prod config.
If the diff adds a new bypass, ask for the rollback plan. How does the team re-enable limiting if the bypass causes an incident? Also check that admin or internal service tokens are still rate limited at a higher ceiling rather than excluded entirely. Compromised internal credentials are a real attack vector, and unlimited internal access is a risk that's easy to overlook.
For more on spotting logic bugs that hide in plain sight, How to Spot Logic Bugs in a Pull Request Before They Hit Production covers the pattern-recognition side of that work.
6. Practice This on Real Pull Requests
Reading about what to look for is not the same as catching it under pressure. The only way to build the reflex is to review real code and get graded on what you missed.
Goodcatch is a code-review practice app that gives you real pull requests, grades you on what you caught and what you missed, and tracks your weak spots over time. Stack-specific tracks cover React, Vue, Laravel, Django, Rails, Node, Spring Boot, and more, with eight reviews per track across multiple difficulty tiers.
You can try three graded reviews free in the browser with no account required. Unlimited access, all difficulty tiers, and weak-spot analytics cost $19 a month, $45 for a three-month sprint, or $144 a year.
If you're also thinking about how your review habits need to shift as you take on more senior work, What Changes About Code Review When You Move From Mid-Level to Senior is worth reading alongside this checklist.
A reviewer who has already missed a sliding-window bug in a practice environment catches it in production. That's the point.