Designing UI/UX around 'processing' states, idempotent consumers for duplicate event delivery, read-your-own-writes techniques, conflict resolution, and the four-point consistency-model taxonomy.
Published September 23, 2026
Saga Pattern and the Outbox Pattern both accept eventual consistency as their tradeoff for availability and loose coupling. This lesson covers the practical design techniques that make eventual consistency actually livable for users and correct for consumers.
User clicks "Place Order"
→ UI immediately shows: "Order placed — processing payment..."
→ (payment confirmation arrives asynchronously, seconds later)
→ UI updates to: "Order confirmed!"
Rather than making the user wait (blocking) until every saga step completes, an honest intermediate UI state — explicitly showing "processing," not silently pretending the action is already fully complete — sets correct expectations and avoids the UX failure of either an artificially long wait or a premature "success" that later turns out to be wrong. This is a genuine product/UX design decision, not purely a backend concern — the backend eventual-consistency model and the frontend's state representation need to be designed together.
@KafkaListener(topics = "order-events")
void handleOrderCreated(OrderCreatedEvent event) {
if (processedEventRepository.existsById(event.getEventId())) {
return; // already processed this exact event — safe no-op, not a duplicate side effect
}
doActualWork(event);
processedEventRepository.save(new ProcessedEvent(event.getEventId()));
}
Given the Outbox Pattern's at-least-once (not exactly-once) delivery guarantee, every consumer needs to handle receiving the same event twice without a bad outcome — tracking processed event IDs and short-circuiting on a repeat is the standard fix, directly analogous to Payment — Idempotency Implementation's idempotency-key deduplication, just applied to event consumption rather than API requests.
When a user expects to immediately see their own change (they just updated their profile, and the very next page load should reflect it), naive eventual consistency can violate this — a read hitting a lagging replica (see Sharding vs Partitioning vs Replication's replication-lag discussion) might not reflect a write the same user just made. Common fixes: route a user's reads to the primary (or a replica known to be caught up) immediately following their own write, or have the client optimistically update its local state immediately on submit, independent of what the backend read path returns moments later.
When two updates to the same data happen concurrently in a system that allows temporary divergence (common in AP-leaning systems — see CAP Theorem), resolving the conflict needs an explicit strategy: last-write-wins (simplest, risks silently discarding one update — the same tradeoff named in Design a Distributed File Storage System's conflict-copy discussion), application-specific merge logic (e.g. merging two concurrent shopping-cart updates by unioning their items, rather than picking one wholesale), or surfacing the conflict to a human/user to resolve manually. There's no universally correct choice — it depends entirely on whether silently picking a winner is an acceptable loss for that specific data.
Interviewers expect fluency navigating this spectrum — not memorized definitions, but knowing which guarantee a given user-facing scenario actually needs, since stronger guarantees always cost more (in latency, coordination, or availability) than weaker ones.
Q: Is monotonic reads a special case of causal consistency, or a genuinely separate guarantee? A: A related but distinct, narrower guarantee — monotonic reads only constrains what a SINGLE client sees over successive reads (never regressing), while causal consistency constrains ordering across DIFFERENT causally-related writes, potentially observed by different clients — a system can provide one without strictly implying the other, though many real systems provide both together.
Q: How would you test that a consumer is genuinely idempotent, not just idempotent by accident under normal conditions? A: Deliberately replay the same event multiple times in a test (including out of the normal delivery order, if the system doesn't guarantee ordering) and assert the resulting state is identical to processing it once — testing only the happy-path single-delivery case would never catch an idempotency bug that only manifests on genuine duplicate delivery.
Q: Does 'processed event ID tracking' for idempotent consumers need its own cleanup strategy? A: Yes, same unbounded-growth concern as the outbox table itself — processed-event records need a retention window (keep only long enough to plausibly catch a duplicate redelivery, which is usually a bounded window, not forever) rather than accumulating without limit.
Q: How does 'optimistic client-side update' interact with a write that ultimately fails (e.g. the saga later compensates and rolls back)? A: The client needs to handle a later correction — showing the optimistic state immediately, then reconciling to the actual eventual state once it's confirmed (including rolling back the optimistic UI if the backend operation ultimately failed and compensated) — this is a real added complexity optimistic UI updates introduce, worth weighing against the responsiveness benefit for the specific interaction.