Reviewing a pull request that changes retry and backoff behavior takes about 20 to 40 minutes done properly. You're checking that the new logic won't hammer a failing dependency, won't silently swallow errors, and won't leave the system in a worse state than before the change. The checklist below walks you through it step by step.
Most retry PRs arrive looking simple: a loop, a delay, maybe a max-attempts constant. The risk is what's hidden underneath. A reviewer who skips the edge cases ships a service that floods a degraded downstream with thousands of redundant requests the moment something goes wrong.
1. Understand what the PR is changing and why
Before reading a single line of diff, read the PR description. A good description names the problem being solved: a dependency was timing out, a queue was being exhausted, a third-party API was returning 429s. If the description is missing or vague, ask for one before reviewing further.
Check the linked issue or ticket. Retry and backoff changes often appear after an incident, which means there's a postmortem or a Slack thread that explains the exact failure mode the author is fixing. That context tells you whether the solution actually addresses the root cause or just papers over it.
You'll know this step is done when you can summarise in one sentence what broke and what the PR does about it.
2. Verify the backoff strategy is sound
Exponential backoff with jitter is the standard approach for retrying against shared infrastructure. A fixed delay retries all callers in lockstep, which means they all hit the recovering dependency at the same moment. Jitter spreads that load.
2a. Check the formula
Look for the delay calculation. A correct exponential-with-jitter formula looks roughly like min(cap, base * 2^attempt) * random(0.5, 1.0). If the code multiplies a fixed constant by attempt count rather than raising it as an exponent, the backoff is linear, not exponential. That's a catch worth making.
2b. Check the cap
Every backoff implementation needs a maximum delay. Without one, a long-running retry loop can eventually produce a delay measured in hours or days, which is almost never what the author intended. Confirm the cap is present and that its value is sensible for the context: a few seconds for a user-facing request, a few minutes for a background job.
3. Count the failure modes the PR introduces
Retry logic is one of the easier places to accidentally introduce new failure modes alongside the fix. Work through each of these before approving.
Retry on the wrong errors. The code should only retry on transient errors: network timeouts, 429s, 503s. It should not retry on 400s, 401s, 403s, or 404s. Retrying a bad request wastes cycles and hides a bug. Check that the retry condition is an allow-list of transient codes, not a catch-all that includes client errors.
No timeout on the total retry budget. Individual attempt timeouts are not enough. The PR needs a total deadline or a maximum attempt count so the whole operation eventually fails. A loop that retries indefinitely will keep a thread or goroutine alive until the process is killed.
Retrying non-idempotent operations. If the operation creates a record, charges a card, or sends an email, retrying it on a timeout could execute it twice. Check whether the downstream operation is idempotent. If it isn't, the PR needs to either make it idempotent or skip retries for that operation type. This overlaps with what you'd examine when reviewing an external API integration, and the questions are worth asking in both contexts.
4. Check what happens when all retries are exhausted
A retry loop that silently returns an empty result is worse than one that fails loudly. When the last attempt fails, the code should do one of three things: return a clear error to the caller, trigger a fallback path, or enqueue the work for later processing. Check which of those the PR actually does.
Look at the error type or message returned. It should indicate that retries were exhausted, not just that the last attempt failed. That distinction matters for on-call engineers reading logs during an incident. Logging and observability around retry exhaustion are worth scrutinising here, because a silent failure at this boundary is one of the harder incidents to diagnose.
5. Read the tests
Test coverage on retry logic is where many PRs are thin. Look for tests that cover: the happy path where the first attempt succeeds, at least one transient failure followed by a successful retry, and the case where all retries are exhausted. If any of those three cases has no test, note it in your review.
Also check whether the tests mock time. Retry tests that use real sleep calls slow the test suite and flake under load. A test that sleeps for real across five retry attempts with 30-second delays will time out in CI. The PR should use a fake clock or a configurable delay function.
6. Confirm observability is in place
Every retry attempt should produce a log entry or increment a metric. Without that, a spike in retry volume is invisible until the downstream starts falling over. Check that the PR logs the attempt number, the error that triggered the retry, and the delay before the next attempt.
If the service already has structured logging, confirm the retry logs follow the same format. A mixed-format log is harder to query than no log at all.
Frequently asked questions
Should I reject a PR that uses fixed delays instead of exponential backoff?
Not automatically. Fixed delays are acceptable for low-volume, non-shared dependencies where lockstep retries don't create a stampede problem. Flag it, explain the trade-off, and let the author decide with full information. A rejection without an explanation of the risk isn't useful.
What if the PR wraps an existing retry library?
Check that the library is configured correctly rather than reading its internals. Libraries like tenacity in Python, retry in Go, or spring-retry in Java expose the same parameters: max attempts, backoff multiplier, jitter, and retryable exceptions. Verify those configuration values exactly as you would hand-written logic.
How do I review retry behavior in async or event-driven code?
The same principles apply, but the failure boundary moves. In an async system, exhausted retries often produce a dead-letter queue entry rather than an exception. Check that dead-letter handling exists, that the entry carries enough context to be replayed manually, and that the queue is monitored.
Practice spotting exactly these issues on real pull requests with Goodcatch.