The problem: one timeout is not a resilience strategy
A production API rarely performs one isolated operation. It accepts a request, authorizes it, reads data, calls one or more dependencies, serializes a response, and returns through an upstream proxy. If every layer owns an independent timeout, the final behavior is accidental: an inner call can continue after the client has left, a retry can consume the entire request window, or an upstream gateway can terminate the response before the application records a useful failure.
A timeout budget makes that behavior explicit. Start with the maximum end-to-end time the caller can tolerate, reserve a safety margin, and divide the remainder among the operations that must complete. The budget is a design constraint, not a promise that every request should run until the deadline.
Core rule: every inner deadline must fit inside the remaining outer deadline.
Build the budget from the outside in
Begin with the shortest trustworthy deadline imposed by the caller, gateway, load balancer, or product requirement. Then reserve time for work that is easy to forget: queueing, authentication, serialization, network return, logging, and cancellation cleanup.
A practical worksheet is:
| Stage | Typical question | Budget owner | Evidence to review |
|---|---|---|---|
| Edge and routing | How much time is lost before application code starts? | Platform team | Proxy and ingress latency |
| Application work | Which CPU, validation, and authorization steps are mandatory? | API team | Traces and handler timings |
| Primary dependency | What operation determines success? | Owning service | Dependency percentiles and error rate |
| Optional dependency | Can the response degrade without it? | Product and API teams | Feature impact and fallback rate |
| Response and safety margin | Can we return and record the outcome before the outer deadline? | API team | Serialization and egress timings |
Do not allocate the full outer limit. A safety margin prevents the system from reaching its deadline while still holding resources or attempting to write a response.
Worked example: a two-second product limit
Assume a product operation must normally return within two seconds. The request reads an account record, calls a risk service, and optionally enriches the response.
One reasonable starting budget is:
- 100 ms for ingress, authentication, and routing.
- 250 ms for application work and the account query.
- 700 ms for the required risk-service call.
- 300 ms for optional enrichment, which may be skipped when little time remains.
- 150 ms for serialization and network return.
- 500 ms of reserve for queueing variance, cancellation, and operational safety.
This is not a universal template. It is a hypothesis to test against traces. If the risk service regularly needs more than 700 ms, hiding that fact behind a larger client timeout will usually move pressure elsewhere. Investigate the dependency, change the product behavior, or redesign the operation.
Retries spend the same budget
A retry is another attempt inside the original operation, not a new request window. Before retrying, calculate whether enough time remains for backoff, execution, and a safe response. If not, stop and return the most useful failure the contract allows.
Retries are most defensible when all of the following are true:
- The failure is plausibly transient.
- The operation is safe to repeat or protected by idempotency.
- The next attempt can finish inside the remaining deadline.
- Retry volume will not amplify an overloaded dependency.
- The result is more valuable than a fast, explicit failure.
Use jittered backoff to reduce synchronized retries. Cap attempts with both a count and the remaining time. Never retry a validation error, authorization failure, or deterministic business rejection.
Propagate cancellation, but keep cleanup bounded
Cancellation should flow from the incoming request to database and HTTP operations. This releases capacity after a caller disconnects or an upstream deadline expires. It also gives traces a coherent reason for incomplete work.
There are two important exceptions:
- Critical cleanup may need a short, independent deadline so resources are released safely.
- A durable side effect should not depend on a browser or client remaining connected. Persist it to a queue or transaction boundary and let a background processor finish it.
The exception must be narrow. Replacing the request token everywhere with an unlimited token defeats the purpose of propagation.
Distinguish timeout symptoms from causes
A timeout says that a deadline expired; it does not identify why. During incident triage, correlate the timeout with:
- Queue depth and thread-pool or connection-pool saturation.
- Dependency latency and error rate.
- Database blocking, lock waits, and slow query shape.
- Garbage collection pauses and CPU throttling.
- Retry volume and circuit-breaker state.
- Deployment, configuration, and traffic changes.
A trace should show the outer budget, the remaining budget at each dependency call, and the component that ended the operation. Avoid logging a routine cancellation as an unhandled application defect, but preserve enough context to distinguish caller cancellation from an internal deadline.
Degradation rules should be designed before the incident
Optional work needs an explicit fallback. Examples include omitting recommendations, returning a cached summary, or marking enrichment as temporarily unavailable. The API contract should make that degradation visible so consumers do not mistake partial data for complete data.
Required work needs a consistent error contract. Choose status codes and problem details that distinguish invalid input, unavailable dependencies, and deadline exhaustion without leaking sensitive internals.
Validation plan
Test the budget under controlled failure, not only on a healthy laptop:
- Add latency to each dependency independently.
- Exhaust a connection pool and observe queueing.
- Make a dependency return transient and permanent failures.
- Disconnect the caller and confirm downstream work stops.
- Trigger retries under load and verify they do not create a traffic multiplier.
- Confirm degraded responses remain contract-valid.
- Check that dashboards identify which deadline fired.
Compare p50, p95, and p99 behavior, but do not optimize a timeout from percentiles alone. Include product tolerance, capacity, and the cost of abandoned work.
Review checklist
- The outer deadline is known and measured.
- Inner timeouts are shorter than the remaining outer budget.
- A safety margin is reserved.
- Retries share the original budget and use jitter.
- Repeatable operations are idempotent.
- Cancellation reaches downstream I/O.
- Durable work survives client disconnects.
- Optional work has a documented degradation path.
- Traces identify the deadline owner and remaining time.
- Load and failure tests verify resource release.
Interview prompts
- Why can increasing a client timeout make an overloaded system less reliable?
- How would you divide a deadline across two required dependencies and one optional dependency?
- When should cleanup ignore the request cancellation token?
- How do retries interact with idempotency and the remaining budget?
- Which telemetry distinguishes a slow dependency from local pool saturation?
A strong answer treats timeout design as resource governance. The goal is not to wait longer; it is to stop predictably, protect shared capacity, and return the best truthful outcome within a bounded amount of time.