Idempotency + Exactly-Once: Design Patterns
Fast recall
Quick revision
Core answer
Depth on distributed-systems correctness, a recurring real-world concern
Confusing idempotency with exactly-once; thinking 2PC solves it; not designing idempotency from day one
Discuss why true exactly-once is impossible, the 'two generals' problem, idempotency-key retention/cleanup, outbox vs 2PC, and the difference between delivery guarantees and processing guarantees
Interview-ready guide
Interview answer
In a distributed system, you can't guarantee a message is delivered exactly once. The canonical proof is the two generals problem: if the sender doesn't get an ack, it doesn't know if the receiver got the message, so it retries. Perfect exactly-once is impossible across a network boundary.
What is achievable: effectively-once
- At-least-once delivery plus idempotent processing
- Message may arrive multiple times; receiver processes it once; subsequent deliveries are no-ops
- From outside, the system behaves exactly-once
The techniques that make processing idempotent
Idempotency keys — the Stripe pattern
- Client generates a UUID per logical operation; sends in Idempotency-Key header
- Server stores the key mapped to response
- Duplicate key returns the cached response
- Critical for POSTs creating resources or transferring money
- Store with TTL around 24 hours; status tracking (IN_PROGRESS, COMPLETED, FAILED)
- DB unique constraint on key prevents race conditions
Natural idempotency
- Some operations are inherently idempotent: DELETE /users/42, PUT /users/42, SET key=value
- Repeating them produces the same state
- Use when the API allows
Optimistic locking with version tokens
- UPDATE ... WHERE id=X AND version=Y
- Second concurrent update finds version incremented; fails
- Caller retries; correct end state
- JPA @Version handles this automatically
Dedupe tables for message consumers
- Small table tracks processed event IDs
- Consumer checks before processing; atomic insert prevents double-processing
- TTL cleanup for old entries
Outbox pattern for transactional publishing
- Write DB change and outbox event in same transaction
- Separate publisher polls outbox and emits events
- Crashes between DB commit and event send are safe; outbox is durable
- Guarantees: if DB commits, event is eventually published
Kafka exactly-once is within Kafka only
- Transactional producer plus idempotent producer gives exactly-once within Kafka
- Read from topic, process, write to topic atomically
- Does NOT extend to external DB writes; your consumer still needs idempotent processing at the DB side
- Advertising 'exactly-once Kafka' and thinking it solves your DB problem is a common misconception
Why 2PC (Two-Phase Commit) is rarely the answer
- XA transactions across DB plus message broker exist
- Fragile, slow, require all resources to support XA
- Modern architectures prefer outbox plus idempotent consumers
Common mistakes
- Thinking idempotency is exactly-once; they're different concepts
- Retrofitting idempotency after API is deployed; requires coordinated client updates
- Clients regenerating UUID per retry; defeats idempotency at server
- No TTL on idempotency keys; table grows unboundedly
- Silent dedupe; no observability on hit rate to catch retry storms
The design discipline
- Day-one design: idempotency keys on all unsafe operations
- Client SDK generates key once per logical operation; reuses on retry
- Server stores key-to-response; duplicate key returns cached response
- Concurrent requests with same key: one processes, one waits or returns 409
- Outbox pattern for event publishing; transactional guarantees
- Dedupe tables on consumers; check-and-insert atomic
Test chaos
- Deliberately inject duplicates, retries, failures in test environments
- Verify no double-charges, double-sends, data corruption
- Hard to test but critical
The strategic answer
- Exactly-once is marketing; effectively-once is engineering
- At-least-once delivery plus idempotent processing is the architecture
- Idempotency keys on client-facing APIs (Stripe pattern)
- Outbox pattern for event publishing
- Dedupe tables on consumers
- Design from day one; retrofitting is coordinated-update pain
Idempotency + Exactly-Once: Design Patterns
Direct Answer
True exactly-once is impossible across network boundaries; aim for effectively-once: at-least-once delivery + idempotent processing = observationally exactly-once. Techniques: Idempotency keys (client-supplied UUIDs stored with results; duplicate keys return the cached response); Natural idempotency (DELETE, PUT by id); Version tokens (optimistic locking: 'update row where version=X'); Dedupe tables (track processed message IDs); Outbox pattern (transactionally write DB change + event; separate publisher drains). Never trust clients to retry safely without idempotency; design it into the protocol.
Common Mistakes & Pitfalls
Confusing idempotency with exactly-once; thinking 2PC solves it; not designing idempotency from day one
Check your understanding
Answer these without looking back at the article.
- How does Kafka's transactional producer enable exactly-once within Kafka?
- What is the Debezium approach to Change Data Capture (CDC) based outbox?
- How do you handle idempotency-key collisions with different payloads?
- When would you use Temporal or Cadence for workflow-level idempotency?
Related interview prep categories
2Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.