In a distributed system, a request can fail in a way that leaves genuine ambiguity: did it never reach the server, or did it succeed and only the response back to the caller get lost? Retrying is the obvious response to a failed request — but retrying safely depends entirely on a specific property called idempotency.

What idempotent actually means

An operation is idempotent if performing it multiple times with the same input produces the same end result as performing it once — repetition doesn’t change the outcome beyond the first successful application. Setting a value to 5 is idempotent: doing it once or five times leaves the same final state. Incrementing a value by 1 is not idempotent: each repetition changes the result further.

Why this matters specifically for retries

Consider a client that sends a request, and the connection fails before a response comes back. The client genuinely cannot tell, from that failure alone, whether the server never received the request, received it but failed before completing it, or completed it successfully and only the response was lost in transit. If the underlying operation is idempotent, the safe response is simple: retry it. Whether the original request actually landed or not, retrying produces the correct final state either way. If the operation is not idempotent, blindly retrying risks applying the effect more than once — the canonical example being a payment: retrying a non-idempotent “charge this card” request risks charging the customer twice for a single purchase.

Idempotency keys: making non-idempotent operations safely retryable

Because many real operations (like creating a new record, or charging a payment) aren’t naturally idempotent, a common pattern makes them safely retryable anyway using an idempotency key — a unique identifier the client generates and attaches to a request, such that the server can recognize a retried request carrying the same key and return the original result instead of performing the operation again. Stripe’s documentation on idempotent requests describes this pattern directly for payment APIs, where it matters most: a client can safely retry a payment request after a network failure, using the same idempotency key, without risking a duplicate charge, because the server recognizes the key and returns the original outcome rather than processing the payment a second time.

Where this connects to retry and messaging patterns

Idempotency is the property that makes the backoff and retry patterns used to prevent retry storms actually safe to use in the first place — retrying with backoff and jitter reduces the risk of overwhelming a struggling dependency, but it’s idempotency that determines whether repeating the operation at all is correct, independent of load concerns entirely. The same property matters directly in message queue systems: most queue delivery guarantees are “at-least-once” rather than “exactly-once,” meaning a consumer has to be prepared to process the same message more than once — which is only safe if the processing logic is idempotent, or made safely repeatable through the same kind of deduplication key used for retried requests.

Designing for idempotency deliberately

Idempotency generally has to be designed in deliberately; it’s not a property most operations have by accident. Practical approaches include using idempotency keys for operations with real-world side effects, designing state changes as “set to this value” rather than “adjust by this amount” wherever practical, and tracking whether a given request or message has already been processed before applying its effect a second time. Treating this as a deliberate design decision, rather than an assumption, is what makes retries and at-least-once delivery something a system can rely on safely rather than a source of subtle, hard-to-reproduce bugs.

Key takeaway

An idempotent operation produces the same result no matter how many times it’s applied, which is exactly the property that makes retrying a failed or uncertain request safe rather than risky. Where an operation isn’t naturally idempotent — payments being the clearest example — idempotency keys let a client retry safely anyway, which is what makes both simple request retries and at-least-once message delivery something a system can depend on rather than a latent source of duplicate side effects.

This article explains a general distributed systems concept; specific implementation patterns vary by system and API. See our disclaimer.