skip to content

In a normalized client cache, what happens when two operations write the same entity with different values?

level: seniorimportance: nice to knowfreq 24%

answer

  1. One entry, many concurrent writers
  2. Merge is shallow and per field
  3. Nothing compares freshness on write
  4. Arrival order, not request order
  5. Watchers re-render on other screens

basics

~20 s

The writes merge field by field into one shared entry, and for any field both carry, the write that lands last wins. Nothing compares freshness, so a slow response can overwrite a newer value and visibly change screens that never issued it.

solid answer

~50 s

A normalized store keeps one entry per identity, so every response mentioning that object writes into the same place. The merge is per field: fields only one response carried are added, and fields both carried are overwritten by whichever write arrives later. There is no version, timestamp or conflict resolution in the general case — arrival order decides. That produces the technique's best property and its worst surprise from the same mechanism. The good part is propagation: refresh one screen and every other view of that object updates for free. The bad part is that arrival order is network order, not request order, so a summary operation issued first but answered second can stamp an older `quantity` over the fresher one a detail fetch just wrote, and the detail screen changes under the user's eyes. When you diagnose an unexplained flicker or revert in a normalized client, the first question is which other in-flight operation also selects that field on that entity.

code

pseudocode · 7 lines
pseudocode
write(entity_key, incoming_fields):
    entry = store.get(entity_key) or new_entry()
    for name, value in incoming_fields:
        entry[name] = value          # no freshness check, last write wins
    store.put(entity_key, entry)
    for read in active_reads_depending_on(entity_key):
        notify(read)                 # views that never issued this operation

go deeper

for a junior

Take away the core fact: there is one entry per object, so any response mentioning it overwrites the fields it carries, and other screens showing that object update too.

for a middle

Be able to state the merge rule precisely — shallow, per field, unconditional — and explain why an entity ends up holding the union of every field any operation has selected for it.

for a senior

Demonstrate the diagnosis: name the entity key and field first, enumerate every in-flight or interval operation that writes it, and reason about arrival order rather than blaming a component.

for a principal

Own the policy. Decide which operation is authoritative for each volatile field across teams, keep it out of incidental selections, and refuse per-screen stores that would trade away the consistency normalization was bought for.

## One entry, many writers The premise of a normalized store is that an object lives in exactly one place. Every response that mentions it — a list query, a detail query, a mutation payload, a subscription message — writes into that one entry. This is not a side effect of the design; it *is* the design, and it is why the technique is worth its cost. But it also means an entity has many concurrent writers and no coordination between them. ## The merge rule Writing an object into the store is a shallow merge of the fields present in this response onto the fields already there. Fields the response did not carry are left alone — a response selecting three fields does not erase the other nine. Fields the response did carry replace what was there. There is no comparison of old and new: the write is unconditional. So for any field two responses both carry, the value in the store is whichever response was *written* last. Not requested last, not generated last on the server — written last on the client. ## Why arrival order is not request order In a warehouse inventory graph spread across an 11-service graph, one operation may resolve entirely from a hot in-memory service while another fans out to inventory, location and audit backends. Two screens open at once issue two operations; the second one issued returns in 40 ms and the first in 610 ms. The client writes them in that order. If both selected `Pallet.quantity`, the value that sticks is the one the *slower* response carried — computed earlier on the server, written later on the client. The user-visible effect is that the detail screen showing `236` briefly settles to `236` and then flips back to `231`, because a background list refresh landed afterwards carrying the older read. Nothing is broken in any single component; the coupling is in the store. ## Why this reaches screens nobody touched Client libraries track which active reads depend on which entity keys, so that a write can notify exactly the affected views. That dependency graph is the delivery mechanism for the consistency benefit — and it is also why an operation on one screen can visibly change another. A background refresh on a dashboard is not scoped to the dashboard; it publishes into the shared store and every mounted view reading those entities re-renders. This is the single biggest mental adjustment for someone coming from per-request caching. There, a component's data belongs to that component's request. Here, a component's data belongs to the store, and the component is a subscriber. ## The related trap: field sets of different fidelity A subtler version is not about timing but about fidelity. Suppose two operations both select `Pallet.status`, but one derives it from a lightweight summary service that reports only `IN_STOCK` or `MISSING`, while the other reads a richer service that also reports `QUARANTINED`. Both write the same field name on the same entity. Whichever lands last defines what the user sees, on every screen. The disagreement is a schema-design problem — one field name carrying two different meanings — but the normalized store is where it becomes visible, because it is the only place the two values ever meet. ## Diagnosing it The symptom is a value that reverts, flickers, or changes on a screen the user did not interact with. The method: 1. Identify the entity key and the field, not the component. The component is a symptom. 2. Enumerate every operation in flight or on a refresh interval that selects that field on that type. That set is your writer list. 3. Look at ordering, not correctness. Each response was probably right when the server produced it. ## What to do about it There is no single fix, which is why this is a judgement question. - **Reduce the writer set.** If only one operation is authoritative for a volatile field, keep that field out of the others' selections. A list that does not select `quantity` cannot stamp on it. - **Prefer server-computed truth on a single path.** Volatile counters are often better fetched by one dedicated operation than incidentally by five. - **Order deliberately where it matters.** Suppress or discard a stale in-flight response when a newer read of the same data has already resolved, rather than letting arrival order decide. - **Give the field a freshness signal.** If the schema exposes a version or an updated-at value on the entity, the client can be configured to ignore a write carrying an older one — but note this is per-type configuration you write, not behaviour a store provides by default. - **Do not reach for isolation.** Giving each screen its own store restores duplicate copies and stale views — you have paid for normalization and thrown away the reason for it. ## The honest summary for an interview Last-write-wins per field is not a defect in normalized caching; it is the price of a single shared truth on the client. Say that plainly, show you know arrival order is what decides, and then talk about narrowing who writes a volatile field rather than about adding a conflict-resolution engine to the client.

  • Does a response selecting only three fields wipe the other fields already stored for that entity?
    No. The merge is shallow and covers only the fields present in the incoming response; anything already stored and not mentioned is left untouched. That is what lets a lean list response enrich the same entry a detail response wrote, and it is also why an entity accumulates the union of every field any operation has ever selected for it.
  • How would you stop a background refresh from reverting a value a detail screen just fetched?
    Narrow the writer set first: if the volatile field is authoritative on one operation, remove it from the others' selections so they cannot stamp on it. Where both genuinely need it, drop or ignore an in-flight response that a newer resolved read has superseded, or key the decision off a version or updated-at field the schema exposes. Per-screen isolated stores are the wrong answer — they discard the consistency you paid for.
  • Two operations write the same field name with values from services of different fidelity. Whose problem is that?
    It is a schema-design problem that the store merely exposes. One field name is carrying two meanings, so whichever response lands last defines what every screen shows. The fix belongs in the schema — one field, one definition, one authoritative source — rather than in client cache configuration, which can only pick a winner after the ambiguity already exists.

It is a shared whiteboard, not a set of notebooks: anyone may overwrite a number, the last hand to write it wins, and everyone in the room sees the change whether or not they asked for it.

saying these in an interview costs you the question

  • Assumes the newest server-generated value always wins
  • Thinks each operation gets its own isolated store
  • Believes a write only affects the screen that requested it
  • Says the store versions every field automatically
  • Thinks a partial write erases unmentioned fields
  • Proposes per-component stores as the fix

context