Skip to content
Search lessons, topics, tests…
Esc

    ↑ ↓ moveEnter openEsc close
    Cheat sheets: all cheat sheets

    Designing Timeout Budgets for .NET APIs: A Practical Worksheet

    A production-focused method for dividing end-to-end API deadlines across application work, dependencies, retries, cancellation, cleanup, and safe degradation.

    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:

    StageTypical questionBudget ownerEvidence to review
    Edge and routingHow much time is lost before application code starts?Platform teamProxy and ingress latency
    Application workWhich CPU, validation, and authorization steps are mandatory?API teamTraces and handler timings
    Primary dependencyWhat operation determines success?Owning serviceDependency percentiles and error rate
    Optional dependencyCan the response degrade without it?Product and API teamsFeature impact and fallback rate
    Response and safety marginCan we return and record the outcome before the outer deadline?API teamSerialization and egress timings

    Do not allocate the full outer limit. A safety margin leaves room for response and cleanup work; it does not itself enforce completion or release resources.

    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:

    1. 100 ms for ingress, authentication, and routing.
    2. 250 ms for application work and the account query.
    3. 700 ms for the required risk-service call.
    4. 300 ms for optional enrichment, which may be skipped when little time remains.
    5. 150 ms for serialization and network return.
    6. 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.

    Recompute, do not restart: the allocations total 2,000 ms. If 800 ms has elapsed before the risk call, preserving 150 ms for the response and 500 ms of reserve leaves 550 ms. Its nominal 700 ms ceiling therefore becomes at most 550 ms, including its retries and waits. With 200 ms of backoff, only 350 ms remains for that call. These are planning calculations; reject or degrade when no useful budget remains, and test actual cancellation latency.

    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

    Pass incoming cancellation to supported downstream operations, and enforce the application deadline explicitly rather than assuming a proxy timeout communicates it. ASP.NET Core request-timeout middleware signals HttpContext.RequestAborted but does not automatically call Abort; the application may still return a response. See request-timeout middleware. Cancellation is a request for cooperation, so verify when work actually completes and capacity becomes available.

    There are two important exceptions:

    • Critical cleanup may need a short, independent deadline and an explicit owner. Do not treat expiry of that deadline as proof that uncooperative cleanup finished.
    • 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:

    1. Add latency to each dependency independently.
    2. Exhaust a connection pool and observe queueing.
    3. Make a dependency return transient and permanent failures.
    4. Disconnect the caller, verify cancellation reaches cooperative downstream work, and measure completion and resource release. Include an uncooperative test double so the test exposes abandoned work rather than assuming it stopped.
    5. Trigger retries under load and verify they do not create a traffic multiplier.
    6. Confirm degraded responses remain contract-valid.
    7. 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.
    • Retry eligibility is justified by operation semantics and an explicit unknown-outcome policy.
    • 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?

    Suggested answers

    1. Longer timeout: in this worksheet, spending more time waiting for risk leaves less time for enrichment and the response. If arrivals continue while earlier work remains in flight, the waiting population grows. Compare those costs with the extra successes before raising the limit.
    2. Two required calls: for serial calls, add their allowances and waits. For independent parallel calls, model the longest path plus join/cleanup time, while accounting for both calls’ resource use. Optional work receives only the remainder after the response reserve.
    3. Cleanup exception: give a necessary cleanup operation a named owner and short separate allowance. Observe its eventual outcome; returning to the caller does not transfer ownership or prove it has finished.
    4. Retry decision: first settle whether replay is safe after an unknown outcome. Then recompute time for delay, attempt, and response; an unused retry count is not extra time.
    5. Telemetry: split queue/permit acquisition from actual dependency execution. A long wait before sending points to local contention; a long remote span points elsewhere. Also record cancellation requested, operation completed, and resources released as distinct observations.

    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.