
Timeouts
When your application server calls another service, it cannot wait forever. If the downstream service is slow or down, blocking indefinitely ties up threads, sockets, and other resources and causes requests to pile up. That slows the server and can eventually crash it. Best practices for timeouts:- Set a sensible per-call timeout (e.g., 1–5 seconds for user-facing APIs). Tune based on observed latency.
- Prefer short timeouts for synchronous user flows; allow longer timeouts for internal background workflows.
- Fail fast: when a timeout triggers, return a clear error so caller logic can decide to retry or take an alternate path.
- Propagate deadline information where possible (for example, include an explicit deadline header) so downstream services can respect caller expectations.
Retries and Backoff
Many errors are transient: a short network blip, a DNS hiccup, or a service restart. Retrying failed calls can often recover automatically. But retries must be performed carefully.
Do not retry blindly. Immediate repeated retries across many clients can worsen outages (retry storms). Limit retries and add backoff and jitter.

- Try immediately once (fast-fail).
- On failure, retry with exponential backoff (e.g., wait 1s, then 2s, then 4s).
- Add jitter (randomized small variance) to avoid synchronized retries across clients.
- Cap the maximum wait and the number of attempts (for example, max 5 tries).
- Only retry on transient errors (network errors, 5xx server errors). Avoid retrying on client errors like 4xx unless you know they are safe to retry.

Idempotency
Idempotency guarantees that performing the same operation multiple times has the same effect as performing it once. This property makes retries safe for state-changing operations. The elevator-button metaphor is helpful: pressing a call button once or ten times only causes the elevator to come once. A common approach is to attach an idempotency key to each client-initiated request. The service stores the state of processed keys and uses them to deduplicate work. Example behavior with idempotency key:- Client sends a write request with
Idempotency-Key: A7Q. - On first receipt of
A7Q: the service performs the action, records thatA7Qhas been completed (and optionally stores the response), and returns success. - On subsequent receipts of
A7Q: the service detectsA7Qis already processed and returns the stored success result without performing the action again.

- Where to store idempotency records: use a durable store that supports fast lookups (a database table, a distributed cache with persistence, etc.).
- TTL for idempotency records: keep them long enough to cover retries (minutes to days depending on use-case) but not indefinitely.
- Store both the outcome and any meaningful response payload you want to replay to the client.
- Ensure the idempotency check and the action are atomic or use a transactional pattern so race conditions cannot lead to double processing.
- Payments and billing operations
- Order creation and inventory reservations
- Any state-changing operation where duplicates are harmful
- Non-critical, cheap operations where duplicates are acceptable
- Operations where deduplication logic would be more complex or costly than the impact of duplicates
Quick Reference
Summary
Combine these three tools:- Timeouts so slow calls fail fast and don’t exhaust resources.
- Retries with exponential backoff and jitter, and a capped attempt count so transient errors can recover without creating retry storms.
- Idempotency so retries of state-changing operations do not create duplicate side-effects.
Timeouts, retries (with backoff), and idempotency are complementary. Timeouts prevent resource exhaustion, backoff-controlled retries handle transient failures, and idempotency makes retries safe for writes.
Links and References
- Exponential backoff (Wikipedia)
- Stripe: Idempotent requests
- AWS Architecture: Exponential backoff and jitter
- Microsoft: Resiliency best practices