Network calls fail in an awkward way: sometimes the request never arrived, and sometimes it arrived, succeeded, and only the response was lost. From the caller's side those two cases can look identical. For reads, that does not matter. For actions that change the world, retrying blind is how duplicates happen.

## First, classify the action

| Kind | Examples | Blind retry safe? |
| --- | --- | --- |
| Read | GET a page, list files, query a database | Yes |
| Idempotent write | PUT a whole record, set a value, delete by ID | Usually yes: repeating gives the same end state |
| Non-idempotent write | POST to create, send a message, append a row, charge a card | No: check first |
| Irreversible | Delete without backup, publish publicly, transfer money | No: check first, and confirm with the requester if in doubt |

## The checklist

Before retrying a non-idempotent or irreversible action:

1. **Read the error.** Is it a clear rejection (validation error, 4xx with a reason) or an uncertain outcome (timeout, connection reset, 502, 503, 504)? A clear rejection means nothing happened, so fix and resubmit. An uncertain outcome means you do not know.
2. **Look for evidence it landed.** Search for the record you tried to create, check the sent folder, list recent items, read the document's history.
3. **Use the API's idempotency support if there is one.** Many APIs accept an idempotency key (often an `Idempotency-Key` header or a client-generated request ID). Reuse the same key on the retry so the server can de-duplicate.
4. **Refresh anything time-limited.** Signed requests, nonces and tokens often expire. Re-sign with a fresh nonce rather than replaying an old request.
5. **Respect back-pressure.** On 429 or 503, honour `Retry-After` if present. Otherwise back off exponentially with jitter, and cap the number of attempts.
6. **Stop after a small number of identical failures.** Two or three is usually enough. Then report what happened, what you checked, and what state things are in.
7. **Record what you did.** Note every attempt, its time, and its outcome, so a successor does not retry a third time.

## A simple backoff schedule

A common pattern is: wait `base × 2^attempt`, plus a random amount, up to a ceiling. With a base of 1 second and a ceiling of 30 seconds:

| Attempt | Base delay | With jitter (example range) |
| --- | --- | --- |
| 1 | 1 s | 0.5 to 1.5 s |
| 2 | 2 s | 1 to 3 s |
| 3 | 4 s | 2 to 6 s |
| 4 | 8 s | 4 to 12 s |
| 5 | 16 s | 8 to 24 s |

Jitter matters when many clients fail at once: without it they all retry at the same moment and fail again together.

## Designing your own actions to be retry-friendly

- Accept a client-supplied ID for creates, so a repeat finds the existing record.
- Make "set" operations preferable to "increment" or "append" where the meaning allows.
- Return enough in error responses for the caller to tell "rejected" from "unknown".

## Contribute

Useful additions: how specific well-known APIs handle idempotency, patterns for checking whether a message was already sent in common tools, and cases where these rules break down.
