Most outages caused by a slow dependency are actually caused by a missing timeout. The dependency degrades from fast to slow rather than failing outright, callers wait, connections accumulate, thread pools fill, and a service that should have shed a fraction of its traffic stops serving anything at all.
The dependency was slow. The outage was the timeout configuration.
What changed in 2026
- Deadline propagation spread beyond service meshes. Passing a remaining-time budget through a call chain, rather than each hop setting its own timeout independently, became more widely implemented.
- Client library defaults improved. More libraries shipped with finite default timeouts rather than infinite ones, reducing the most common configuration gap.
- Timeout auditing became standard. Checking every outbound call for an explicit timeout entered ordinary code review and static analysis.
- Retry interaction got emphasized. Recognition that a timeout plus retries multiplies the effective wait pushed teams to budget the total rather than each attempt.
The settings that matter
| Timeout |
Governs |
Typical mistake |
| Connect |
Establishing the TCP connection |
Set too long; a reachable host connects fast |
| TLS handshake |
Completing the handshake |
Frequently unset entirely |
| Read or socket |
Waiting for data after sending |
The most commonly missing one |
| Total request |
The whole operation including retries |
Rarely set; the one that bounds your exposure |
| Idle connection |
How long a pooled connection stays open |
Affects pool behaviour, not request latency |
| Pool acquisition |
Waiting for a free connection |
Missing this causes queueing under load |
Connect and read are different and both matter. A host that is reachable connects quickly; a long connect timeout only helps when the network is badly degraded, and it delays detecting an unreachable host. A read timeout is the one that catches a dependency that accepted your connection and then went slow, which is the common failure.
Total request timeout including retries is the one nearly nobody sets and the one that bounds your actual exposure. Three attempts at a five-second read timeout is fifteen seconds of waiting, and if your caller gave up after eight, the last two attempts were work nobody will read.
Budgeting from the outside in
Start with what the user will tolerate. That total is the budget for the whole call chain.
Allocate it downward. If the user-facing request has a two-second budget, the service layer might allow one and a half seconds for its downstream calls, and each of those allows less again. Timeouts should decrease with depth, never increase.
The failure mode of increasing timeouts is specific and common: an inner service with a thirty-second timeout continues processing a request whose caller timed out at ten, holding a connection, a thread, and a database transaction for work whose result is already discarded. Under load that is how resources exhaust.
Deadline propagation formalizes this — passing the remaining budget with the request so each hop knows how much time is actually left rather than applying a static configuration. Where the framework supports it, it is strictly better than independent timeouts.
Pair timeouts with a circuit breaker so a persistently failing dependency stops receiving traffic entirely rather than timing out on every request, as covered in circuit breaker pattern. And bound retries so the total stays inside the budget — the amplification concern in backpressure explained.
Common mistakes
- No timeout at all. Many clients default to infinite.
- Timeouts increasing with depth. Inner work outlives its caller.
- Only setting connect timeout. Read timeout is the one that catches slowness.
- Not bounding total time across retries. Effective wait is a multiple of what you configured.
- No pool acquisition timeout. Requests queue invisibly when the pool is exhausted.
- Timeouts set from typical latency. Budget from what the user tolerates.
FAQ
What is a reasonable read timeout?
Derived from your budget, not from the dependency's typical response time. If you have one second to spend, that is your timeout regardless of whether the dependency usually answers in fifty milliseconds.
Should retries share the timeout?
Each attempt needs its own, and the total across attempts must fit the budget. Set both explicitly.
How do timeouts interact with connection pools?
A slow dependency holds pooled connections for the duration of the timeout. Long timeouts plus a small pool means the pool exhausts quickly — see connection pooling explained.
What about long-running operations?
Make them asynchronous with a job identifier rather than holding a request open. Long synchronous operations do not fit a latency budget.
Where to go next
For failure isolation, read circuit breaker pattern and backpressure explained. For pool sizing, connection pooling explained.