A message queue delivers the same order-created message to a consumer twice because the consumer crashed after processing it but before acknowledging it. What must the consumer do so processing the message twice doesn't cause a duplicate charge or duplicate shipment?
answer
- at-least-once -> duplicates possible
- processed-message check before side effect
- business key vs message ID
- Stripe Idempotency-Key header
- exactly-once effect, not exactly-once delivery
basics
~20 sIdempotent means doing something twice has the same effect as doing it once. The consumer remembers which messages it already handled (by ID) and skips reprocessing the effect, like charging money again, even if the message arrives more than once.
solid answer
~40 sAt-least-once delivery means a broker can redeliver a message after a crash, network blip, or unacked processing, so any consumer must expect duplicates. An idempotent consumer makes reprocessing safe by ensuring the net effect of handling the same message N times equals handling it once. Two common techniques: track a unique message identifier in a durable store and skip messages already marked processed, or design the side effect itself to be idempotent, e.g. an upsert by primary key instead of an increment. Without this, duplicate delivery becomes duplicate side effects: double charges, duplicate shipments, double inventory decrements. Idempotency is a consumer-side responsibility because most brokers only guarantee at-least-once delivery, not exactly-once processing; the application must supply the 'exactly-once effect' on top of 'at-least-once delivery'.
go deeper
Should recognize that redelivery happens and that reprocessing must not double the side effect; a vague 'check if we already did it' answer is fine without naming a specific storage mechanism.
Should name a concrete mechanism (processed-message table or unique constraint) and connect it explicitly to at-least-once delivery as the root cause.
Should discuss key selection (message ID vs business key), the check-then-act race, and be able to contrast dedup-store approaches with naturally idempotent operations.
Should discuss this as a system-wide design policy, where idempotency is enforced (producer key generation, consumer dedup store, database constraints), how it interacts with delivery guarantees and transactional outbox patterns, and the cost/benefit of applying it uniformly versus selectively.
## Why the same message arrives twice **At-least-once delivery** is the dominant guarantee in production messaging systems: brokers like Kafka, SQS, RabbitMQ, and Pub/Sub choose to redeliver a message rather than risk losing it, because the alternative, **at-most-once**, silently drops messages on any failure between fetch and acknowledgment. The price of that safety choice is duplicates: a consumer can process a message, start doing side effects, then crash or lose its network connection before it acknowledges (acks) the message, and the broker, having no proof the message was handled, redelivers it. The same order-created message might now arrive at the consumer twice, spaced by anywhere from milliseconds to hours later after a redeploy. ## How an idempotent consumer works **Mechanism:** an idempotent consumer is one whose observable effect from processing a message N times (N >= 1) is identical to processing it once. Concretely, the consumer keeps a record of which messages, or which business operations, it has already applied: - Before applying a new message's effect, it checks that record. - If the message's identity already appears there, it **skips the side effect** — or safely reapplies a value that lands on the same result, such as setting a field to an absolute value rather than incrementing it — and simply acks. The **identity** used for that check matters. It can be: - the message's own unique ID assigned by the producer or broker, or - a business-level key such as an order ID plus an operation type, deliberately chosen so retries of the same producer-side action collapse to the same key. ## The reliability model underneath **Why it exists:** this is purely a consequence of choosing at-least-once delivery as the reliability model. Distributed systems cannot cheaply guarantee exactly-once delivery across a network boundary, so the practical compromise used by essentially every message broker is deliver at least once, and let the consumer make redelivery harmless. 'Exactly-once processing' as marketed by some systems is really 'exactly-once effect,' built by combining at-least-once delivery with an idempotent consumer; the achievement lives in the application logic, not magic in the wire protocol. ## What it costs **Trade-offs:** making a consumer idempotent costs something on both sides. - **On the correctness side**, if you get it wrong and a duplicate slips through, the failure is silent and customer-visible (double charge, duplicate email, doubled inventory decrement), which is often far more expensive to unwind than the extra engineering to prevent it. - **On the engineering side**, idempotency adds a persistent lookup or a schema constraint to every message handled, which costs latency (an extra read or an upsert with a uniqueness check) and storage (a growing table of processed identifiers). Teams sometimes accept a narrower guarantee, duplicates are rare and reconciled nightly, when the operation is cheap to reverse and the volume of true duplicates is low, trading strict correctness for simplicity. ## Where it breaks **Failure modes in production:** 1. **The most common failure is choosing the wrong deduplication key**, for example deduplicating on a broker-generated message ID when the producer itself retried and got a new ID for what is logically the same event, so the duplicate check never fires. 2. **Another is a check-then-act race:** two threads or two consumer instances read 'not yet processed' at nearly the same instant, both proceed, and both apply the side effect before either writes the 'processed' marker; this needs a database-level unique constraint or conditional write, not just an application-level if-check. 3. **A third is unbounded growth of the dedup store:** if processed-message records are never expired, the lookup table grows forever and eventually degrades lookup latency or blows storage budgets. ## The same pattern at an API boundary **A concrete real-world scenario:** Stripe's payment API is idempotent by design: clients pass an `Idempotency-Key` header with every charge request, and Stripe stores the key with the first response for a defined window. If the same key is replayed because the client's HTTP call timed out and it retried, Stripe returns the original charge's result instead of creating a second charge. That is the idempotent-consumer pattern applied at an HTTP API boundary rather than a message queue, and it illustrates the general shape: - identify the logical operation with a stable key chosen by the caller, - remember what happened the first time, - and short-circuit repeats.
- Why can't the message broker itself just guarantee exactly-once delivery and remove the need for this?Guaranteeing exactly-once delivery across a network requires the sender to know for certain the receiver got and durably recorded the message, which needs an expensive handshake that still can't survive every failure combination. Brokers instead pick at-least-once because losing messages is worse than duplicating them, and push the final 'exactly-once effect' onto the consumer, which is in a better position to make it cheap, for example, a database it already owns.
- If the consumer's action is just 'send a notification email,' does it still need idempotency?Yes, duplicate emails are a visible, annoying side effect even though they're not as costly as a double charge. The same processed-message check applies; the bar for 'is this worth the engineering cost' depends on how bad the duplicate outcome is, not on whether the operation touches money.
- What's the difference between deduplication and idempotency?Deduplication is one technique to achieve idempotency: explicitly recognizing and dropping a repeat message. Idempotency is the broader property that repeat processing has the same effect as single processing, and can also be achieved without a dedup store at all, by making the operation itself naturally repeat-safe, for example 'set status to SHIPPED' instead of 'increment ship count'.
Like a bouncer with a guest list who crosses off names as people enter; if the same person's ticket gets scanned twice at the door, the bouncer recognizes the name is already crossed off and doesn't let them cause a second head-count, even though the ticket was scanned twice.
saying these in an interview costs you the question
- Says at-least-once delivery means the consumer will never see a duplicate
- Thinks acking before processing solves duplicates (it causes message loss instead)
- Can't name a mechanism beyond 'just don't process it twice'
- Confuses idempotency with retries/backoff, a different concern
- Assumes the message broker guarantees exactly-once processing out of the box