Backend & Node.js · APIs, data and failure

How do you build an idempotent message consumer?

Hardidempotencymessage queuesdistributed systemsUpdated Oct 2026
Quiz this track

Short answer

Assign every message a stable id, record processed ids in the same transaction as the side effect, and skip duplicates. Make the side effect itself idempotent or use an inbox table so retries and replays cannot double-apply.

With at-least-once delivery, a consumer can see the same message twice: the broker redelivers after a timeout, a rebalance moves a partition, or an operator replays a batch. Idempotency is not optional; it is part of the consumer contract.

sql
BEGIN;

INSERT INTO processed_messages (message_id)
VALUES ('evt_01J8Z3')
ON CONFLICT (message_id) DO NOTHING;

UPDATE orders
SET status = 'paid'
WHERE id = 1042 AND status = 'pending';

COMMIT;
The dedupe insert and the business update commit together. A duplicate inserts nothing and the update is naturally repeatable.

Patterns that work

  • Use a producer-generated message id, not a random value created on each attempt
  • Store processed ids in the same database and transaction as the side effect
  • Make external calls idempotent with an idempotency key
  • Use conditional updates or unique constraints instead of blind increments
  • Retain dedupe records long enough to cover the broker retention and replay window
  • Handle ordering explicitly; a later event must not overwrite newer state

A separate dedupe cache is weaker than a transactional inbox because the cache write and the database commit can diverge. If you must use one, accept a small duplicate window and design the side effect to tolerate it. For payments and inventory, prefer the transactional approach.

Exactly-once processing is usually achieved by combining at-least-once delivery with idempotent effects and deduplication. It is not a property you can assume from the broker alone.

Follow-ups you should expect

Interviewers use these to check whether the first answer was understanding or memorisation.

  1. 1Why is a dedupe cache alone insufficient?
  2. 2How long should processed message ids be retained?
  3. 3How do you handle out-of-order events?
  4. 4What is the difference between idempotent and exactly-once?

Say it in the interview

I give each message a stable producer-generated id and record it in a processed-messages table in the same transaction as the business change. A duplicate hits the unique constraint and the side effect is skipped. For external calls I pass an idempotency key, and for state changes I use conditional updates. A cache-only dedupe can diverge from the database, so I prefer a transactional inbox for critical work.

Use this as a model, not a script. Saying it in your own words is what makes it survive follow-up pressure.

Related questions

Reading is step one

Now say it out loud.

Understanding an answer and delivering it under pressure are different skills. Rehearse this topic in a live Backend & Node.js mock interview and get scored feedback on depth, clarity and confidence.