← All topics
📬 Messaging & NServiceBus

Idempotent Consumers

Why 'at-least-once delivery' forces this, and how to actually implement it.

Because message brokers realistically guarantee at-least-once delivery (see Queue & Message Handling), any consumer will eventually receive the same message twice — a redelivery after a timeout/crash right after processing but before acknowledging, a retry after a transient failure, or simple broker-level duplication. An idempotent consumer produces the same end state whether it processes a given message once or five times.

How to actually implement it — the concrete techniques worth naming: 1. Deduplication table: store processed message IDs (with a unique constraint) and check/insert before processing — if the ID's already there, skip (or short-circuit to the same result) instead of reprocessing. 2. Natural idempotency via upsert semantics: design the operation itself to be safely repeatable — "set order status to Shipped" is naturally idempotent (setting it twice = same end state); "increment stock by 1" is not (doing it twice changes the result) unless guarded. 3. Idempotency keys on the message itself: the message carries a unique key the handler checks against already-applied operations before acting, common for payment/financial operations specifically.

Tie this back to REST: it's the exact same concept as idempotent HTTP verbs (PUT/DELETE), just applied to message handlers instead of endpoints — a good bridge to mention if the interviewer connects the two topics.

Flashcards (3)

Why does at-least-once message delivery specifically force consumers to be idempotent?
tap to reveal answer
At-least-once means duplicate delivery of the same message is a real, expected possibility (redelivery after crash-before-ack, retries, broker duplication) — not idempotent handlers would then double-apply effects (e.g. double-charging, double-incrementing stock) on those duplicates.
Name three concrete techniques for making a message handler idempotent.
tap to reveal answer
1) A deduplication table storing processed message IDs, checked before processing. 2) Designing the operation itself to be naturally idempotent (e.g. 'set status to X' rather than 'increment by 1'). 3) An idempotency key carried on the message, checked against already-applied operations before acting.
Give an example of an operation that is naturally idempotent vs one that isn't, in a messaging context.
tap to reveal answer
Naturally idempotent: 'set order status to Shipped' — applying it twice leaves the same end state. Not idempotent by default: 'increment stock count by 1' — applying it twice changes the result, so it needs an explicit guard (e.g. a dedup check) to be made idempotent.