Skip to content

Taxonomy

Retry without idempotency

It was retried, and it worked twice.

What it looks like

  • A retry wrapper around an operation that creates or charges something.
  • A retry on a timeout, where the first attempt may have succeeded after the client gave up.
  • An idempotency key generated inside the retry loop rather than outside it.

Why it survives review

The retry is obviously correct — the code retries, the flake goes away, the metrics improve. The double effect only happens when the first attempt actually succeeded and the *response* was lost, which is rare and looks like a different bug when it happens.

How to see it

  1. For every retried operation, ask whether running it twice is the same as running it once. If not, the retry needs a key.
  2. A timeout is not a failure. It means you do not know, and 'do not know' must not be retried like 'did not happen'.
  3. Check the idempotency key is generated once per logical operation, not once per attempt.

A minimal pair

Correct

const key = newIdempotencyKey();
return withRetry(() => charge(amount, key));

Defective

return withRetry(() => charge(amount, newIdempotencyKey()));

A charge that times out and is retried is now two charges, each with its own key.

Practise it

No exercise in the corpus sets this class yet. The lesson stands on its own — the corpus grows by adding subjects, and pretending otherwise would hide the gap.