What it means
Exponential backoff is what a well-behaved client does after a request fails: wait, try again, and if it fails again, wait longer. The waits grow geometrically, usually doubling, until they hit a cap or the client gives up.
The idea is older than the web. Ethernet's binary exponential backoff, part of the CSMA/CD standard, resolves collisions on a shared wire by having each station wait a random multiple of a doubling slot time. TCP does the same with its retransmission timeout, doubling the RTO on each loss as described in RFC 6298. HTTP clients inherited a pattern that already had two decades of evidence behind it.
The reason it works is worth stating precisely, because it is not "be polite". A failing service is usually failing because it is saturated. Every retry sent during that window is load added at the worst possible moment. Backoff makes the aggregate retry load decay geometrically, which gives the service headroom to recover. Constant-interval retries do the opposite: they hold the load flat and can keep a service down long after the original trigger has passed.
The arithmetic nobody does
Almost every article about backoff stops at the formula. The table below runs it: base 500 ms, multiplier 2, cap 60 seconds. The column that matters is the cumulative one, because that is the number your operations team actually cares about, and it is invisible in the formula.
Eight retries at those settings spend 123.5 seconds sleeping. That is your real deadline: if the operation has to complete within two minutes, eight retries is already too many, and no amount of tuning the multiplier fixes it. Ten retries takes 243.5 seconds and only adds two more chances, because everything past retry 8 is a flat 60-second poll.
The second piece of arithmetic is retry amplification. A uniform policy of three retries means four attempts per operation. During a total outage, when every attempt fails, the service receives four times its normal request volume from a client base that is doing nothing unusual. This is how a five-minute partial degradation becomes an hour-long outage, and it is the argument for a retry budget: cap retries as a fraction of successful traffic, typically ten percent, and drop the rest rather than sending them.
What to retry, and what never to retry
The status code decides. Retrying a 422 is a loop that cannot terminate successfully; not retrying a 502 throws away a request that would probably have worked the second time.
One case is worth more than a row in a table. The Pinlyx Data API returns 503 for three genuinely different conditions and expects you to branch on error.code rather than on the status:
upstream_timeout: a source was too slow. This is also what a catalogue query hitting its 15-second statement timeout returns, so it can reach you from any endpoint that reads the corpus, not only the live ones. Retrying with backoff is worthwhile, and narrowing the request (a smaller limit, a tighter filter, a less popular account) makes it far less likely to recur.upstream_error: a source was unreachable. Back off further than you would for a timeout.not_configured: the capability has no backing service in this deployment. Retrying will never help. A client that treats all 503s alike will retry this one until its budget is gone, every single time.
That is the general lesson: the status code is a category, the error code is the diagnosis. Any API worth integrating with gives you both.
Backoff is not the whole answer
Three things belong next to it.
Jitter. Pure exponential backoff synchronises clients. Everyone who failed at time T retries at T plus 1, then at T plus 3, then at T plus 7, in unison. The retry curve becomes a series of spikes, each as tall as the original burst. Randomising the delay flattens them, and it is one line of code. See jitter for the three standard strategies and their trade-offs.
A deadline, not just an attempt count. "Five retries" means something different for a 40 ms call than for one that times out after 30 seconds. Track total elapsed time and abandon on that, with the attempt count as a secondary guard.
A circuit breaker. Backoff still sends a request per attempt. When a dependency has been down for ten minutes, sending anything is waste. A breaker that opens after N consecutive failures, fails fast without touching the network, and half-opens periodically to probe, removes that waste entirely and gives the recovering service a much gentler ramp.
A fixed ladder can beat a computed curve
Not every retry schedule should be a formula. The Data API's webhook delivery pipeline retries a failed delivery up to 8 times on a published, fixed ladder: 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours, 24 hours, and then the delivery is marked failed. It is never retried forever.
The step ratios there are 5, 6, 4, 3, 2 and 2, which is not exponential at all. That is deliberate. The thing a webhook receiver actually needs to survive is not a smooth mathematical curve, it is a set of human events: a deploy, a night, a weekend morning. A ladder whose total span is 44 hours 36 minutes covers a receiver that broke on Friday evening and was fixed on Sunday. A doubling curve capped at 60 seconds would have given up in the first two minutes.
The corollary matters for receivers: your de-duplication store has to outlive the sender's ladder. If the sender can re-deliver 44 hours after the first attempt and you keep processed event ids for 24 hours, the last retry is processed twice. Seven days is the safe number here.
Related concepts
- Jitter: the randomness that stops backoff from synchronising clients.
- Retry-After: the server's own instruction, which outranks your formula.
- Rate limit: the most common reason a retry is refused.
- Idempotency key: what makes retrying a write safe rather than merely slower.
- Flood wait: Telegram's version of "wait this long", with its own backoff rules.
How Pinlyx handles it
Retries with backoff are built into both directions of the product. Outbound messages that hit a platform flood wait are requeued with an automatic backoff rather than failing the job. Webhook deliveries follow the published 8-attempt ladder above and expose every attempt, its status code, its error and its duration, so a failed delivery can be diagnosed rather than guessed at. And every Data API error carries a stable code so a client can tell "try again in a moment" apart from "this will never work".