Independent timeouts multiply latency. An end-to-end deadline turns the remaining caller budget into a system-wide constraint.
Request Deadline Propagation
Downstream work inherits the caller’s remaining time; it does not receive a new budget.
async function withDeadline<T>(parent: AbortSignal, remainingMs: number, work: (signal: AbortSignal) => Promise<T>) {
if (remainingMs <= 0) throw new Error('deadline exceeded')
const signal = AbortSignal.any([parent, AbortSignal.timeout(remainingMs)])
return work(signal)
}
await withDeadline(request.signal, remainingMs, signal => fetch(url, { signal }))Invariant: No downstream operation outlives the remaining end-to-end budget.
Use when: Downstream work must not outlive the caller’s latency budget.
Why this boundary matters
Independent per-hop timeouts accumulate and leave orphan work after callers depart. One deadline bounds the complete synchronous call tree.
Failure policy
| Boundary | Action |
|---|---|
| Budget remains | Pass the smaller remaining budget downstream |
| No useful budget remains | Fail before starting more work |
| Child operation times out | Abort it and propagate a typed deadline outcome |
| Caller cancels | Cancel all owned downstream work immediately |
| Background work must survive | Detach it through a durable queue, not the request context |
Trade-offs
Deadlines prevent orphan work and cap tail latency, but budgets that are too tight create self-inflicted failures. Per-hop timeouts must shrink from one end-to-end deadline rather than accumulate independently.
Decision rule: Propagate a deadline across synchronous work; move work to a durable asynchronous boundary when it must outlive the caller.
Further reference
Browse all engineering snippets · Read about retry amplification