List the layers in the order a request passes through them, starting with whatever the caller sees. These values usually live in three different repositories owned by three teams, which is most of the reason nobody has ever seen them side by side.
| Layer (outermost first) | Timeout s | Attempts | Backoff s | Remove |
|---|---|---|---|---|
| Layer | Timeout | Attempts | One attempt costs | Needs | Status | Attempts that complete |
|---|---|---|---|---|---|---|
| API gateway | 30 s | 1 | 1.7 min | 1.7 min | starved | 0 of 1 |
| Calling service | 25 s | 3 | 34 s | 1.7 min | starved | 0 of 3 |
| HTTP client | 10 s | 3 | 11 s | 34 s | starved | 0 of 3 |
| Database driver | 5.0 s | 2 | 5.0 s | 11 s | starved | 1 of 2 |
Outermost timeout required, by retries and depth
What the top of the stack has to allow for every attempt beneath it to complete, given a two second leaf operation and one second of backoff per layer. Read across a row to watch the requirement explode, and note that the first row is flat: with no retries anywhere, depth costs nothing.
| Attempts per layer | 1 layer deep | 2 layers deep | 3 layers deep | 4 layers deep | 5 layers deep |
|---|---|---|---|---|---|
| 1 (no retries) | 2 s | 2 s | 2 s | 2 s | 2 s |
| 2 | 5 s | 11 s | 23 s | 47 s | 1.6 min |
| 3 | 8 s | 26 s | 1.3 min | 4.0 min | 12.1 min |
| 4 | 11 s | 47 s | 3.2 min | 12.8 min | 51.2 min |
Amber is over 30 seconds, red is over two minutes. Three attempts at four layers deep needs four minutes at the top for a two second operation, which is why the fix is nearly always fewer retry layers rather than a longer outer timeout.
A worked example
Four layers, each configured by a different team, each with a number that looks sensible in isolation. A 30 second gateway, a 25 second service with three attempts, a 10 second HTTP client with three attempts, and a 5 second database driver with two. Nobody has ever seen them in one place.
There are no inversions: the timeouts do decrease inward, which is the check most people would run and the only one that passes. The problem is underneath. One attempt at the HTTP client costs the driver's full budget of 10.5 seconds, one attempt at the service costs 33.5 seconds, and the stack as a whole needs 104.5 seconds against a gateway that allows 30.
So every one of the four layers is starved, and the number that makes it concrete is the attempt count: the configuration asks for 18 attempts and exactly one of them can complete inside the budget. The other seventeen are either never reached or cut off mid-flight, and the ones that do reach the dependency still consume its capacity on the way.
The arithmetic says the gateway would need 104.5 seconds. That is the honest number and it is the wrong fix: nobody waits a hundred seconds, and it would triple the load on a dependency that is already failing. Cutting retries alone is not enough either, because the leaf is expensive: dropping the service and client to two attempts each still needs 46 seconds. Two changes together do it. Take the driver timeout from five seconds to two, and the service and client to two attempts each, and the stack needs 22 seconds, which fits inside the existing 30 with headroom, at 8 attempts instead of 18. The ten retries that disappear were never completing anyway.
The arithmetic, so you can check it
Worked from the innermost layer outward. One attempt at any layer costs the full budget of everything beneath it, so a layer needs inner budget x attempts + backoff x (attempts - 1). The innermost has no child, so one attempt there costs at most its own timeout.
A layer is inverted when its timeout is at or above its caller's, which means it can never fire. It is starved when what it needs exceeds what its caller grants. Attempts that complete is floor((granted + backoff) / (one attempt + backoff)), capped at the attempts configured.
This computes the worst case, where every attempt runs to its full timeout. Real traffic mostly succeeds on the first attempt and never approaches these numbers, which is exactly why the configuration survives for years and then fails all at once during a dependency slowdown, when the worst case becomes the normal case. It also assumes the timeout is enforced on the whole attempt rather than on socket idle time, which differs by client and is worth checking on yours.
The multiplication and the retries nobody counts are in idempotency for money movement. For the deduplication window those attempts require, use the idempotency window calculator.
Questions
Decreasing inward, with each layer's budget large enough for the full inner budget times its own attempt count plus its own backoffs. The common mistake is setting each timeout independently to a number that sounds reasonable, which produces a ladder where the inner layers can never finish what they are configured to attempt.
Because each outer attempt re-runs the entire budget beneath it. Three layers at three attempts each is twenty-seven attempts for one request, and the time required grows the same way. Every individual layer is correctly inside its own limit, nothing is violated, and the total is a number nobody could state from memory.
An inner timeout at or above the timeout of the layer calling it. The inner limit can then never fire, because the outer one always fires first, so every failure at any depth presents as the same outer timeout and the trace tells you nothing about where it happened.
Because the budget granted by the layer above is smaller than the time your attempts need. Those attempts still reach the dependency, still consume its capacity and still count against its rate limit, so the configuration is buying load rather than resilience. The tool reports how many of your configured attempts actually fit.
Almost always cut the retries. Raising the top to accommodate a multiplied retry budget produces timeouts measured in minutes for requests a user is waiting on, and it does nothing about the load those attempts put on a dependency that is already struggling. Removing one retry layer costs nothing and divides the requirement.