Integration Patterns

Request Timeouts and Deadlines

Never wait forever — bound every call and every overall operation with an explicit limit.

What you'll learn

  • Explain why every external call needs a timeout
  • Distinguish a per-request timeout from an overall deadline
  • Choose timeout values that balance patience and responsiveness
  • Propagate deadlines across chained calls
  • Handle a timeout as a distinct outcome

6 min

Why timeouts are mandatory

Every call that leaves your process can hang. A network can stall, a server can wedge, a connection can be silently dropped. Without a timeout, a single stuck call ties up a thread or connection indefinitely, and enough of them exhaust your resources and take your service down.

The default in most libraries is to wait forever, which is almost never what you want. An explicit timeout converts an indefinite hang into a bounded, handleable failure. Treat a missing timeout as a bug: every outbound call should have one. The question is never whether to set a timeout but what value is appropriate for that call.

Request vs deadline

There are two related limits. A per-request timeout bounds a single attempt. An overall deadline bounds an entire operation, including any retries and intermediate steps.

The distinction matters once retries are involved. A 10-second per-attempt timeout with three retries could still consume far longer in total. An overall deadline says this whole operation must finish within X, regardless of how many attempts it took. Use both: the per-request timeout stops any one call hanging, and the deadline stops the combined sequence running away. The deadline is what a waiting user or upstream caller actually cares about.

Choosing values

Timeout values are a balance. Too short, and you abandon calls that would have succeeded, manufacturing failures. Too long, and a genuinely stuck call holds resources far longer than necessary.

  • Base it on observed latency — set above the normal high percentile, not the average.
  • Tighten interactive paths — a user waiting needs a shorter limit than a background job.
  • Separate connect and read — failing to connect should give up faster than a slow response.

Review timeouts against real latency data periodically; a value that was right at launch may be wrong as traffic and dependencies change.

Propagate and handle

When one request triggers a chain of downstream calls, propagate the remaining deadline through them. If only two seconds are left, no downstream call should be allowed to wait ten. Passing the deadline down prevents work continuing on a request the caller has already abandoned.

remaining = deadline - now()
if remaining <= 0:
    raise DeadlineExceeded()
downstream.call(request, timeout=remaining)

Treat a timeout as its own outcome, distinct from a clean failure. Crucially, a timeout does not tell you whether the work happened — the request may have succeeded with a lost response. That is exactly why timed-out writes need idempotency before they are retried.

Key takeaways

  • Every outbound call needs an explicit timeout; the default is often forever
  • A per-request timeout bounds one attempt; a deadline bounds the whole operation
  • Base timeout values on observed latency, tighter for interactive paths
  • Propagate the remaining deadline through chained downstream calls
  • A timeout does not confirm whether the work happened — retry writes idempotently

FAQ

Why not just let calls wait as long as needed?

Because a stuck call holds a thread or connection indefinitely, and enough of them exhaust your resources and crash the service. A timeout converts an indefinite hang into a bounded failure you can handle.

What is the difference between a timeout and a deadline?

A per-request timeout limits a single attempt. An overall deadline limits the entire operation including retries. With retries in play, you need the deadline so the combined sequence cannot run far longer than intended.

Does a timeout mean the work did not happen?

Not necessarily. The request may have succeeded with the response lost in transit. That uncertainty is why a timed-out write should be made idempotent before retrying, so a duplicate has no extra effect.

Integrate with Merion

Ready to build?

Read the API reference, grab the OpenAPI spec, and ship a resilient integration.