A store hands back only the bytes it was given, and two services each rewrite one profile document: which fixes for an overwritten change remain?
answer
- ask what the server can read
- whole-value writes, not field edits
- a marker checked at write time
- create-if-absent plus a deadline
- refusal beats silent loss
basics
~20 sMoving the change onto the server is unavailable, because the server cannot interpret bytes it only stores and returns. What remains is a version token checked on write, a conditional create, or an expiring claim held around the whole read-modify-write cycle.
solid answer
~50 sThe remedies for an overwritten change split on one question: can the server read the value? Where it can — a counter, a map field, a set member — the cheapest fix is a **server-side in-place update**, one round trip with no gap to protect. Here it cannot, so every change is a full read-modify-write and that remedy is off the table. Two remain. A **version token** is an opaque marker read alongside the value and presented on write; the store refuses the write if the entry changed since, and the caller re-reads and tries again. An **expiring claim** — a conditional create of a marker entry, with a deadline and an owner marker — lets one caller at a time run the cycle. The token costs wasted attempts under contention; the claim costs waiting, and needs a deadline longer than the work it guards.
go deeper
Recall that when the server only stores and returns bytes, changing anything means reading the whole value, editing it in your own code, and writing all of it back — so two writers can overwrite each other's edits entirely.
Explain the split: which remedies need the server to interpret the value and which do not. Name the version token and the expiring claim as what remains, and say what each one costs when two callers collide.
Show the judgment: pick the remedy from the contention rate and the cost of a lost attempt, and say when the real fix is to split one large document so independent writers stop sharing an entry at all.
Treat the capability question as an architectural constraint, not a detail. If the design depends on the server interpreting values, you have coupled yourself to one class of store; decide whether that coupling is worth it or whether the invariant belongs elsewhere.
## The constraint that decides everything Stores in this class split on how much the server understands about a value: - Some servers **interpret structure**: the value is a counter, a map with named fields, a set of members, an ordered collection, and the server can read or change part of it on request. - Others treat every value as **opaque bytes**: they store what you hand them and return it unchanged, and have no idea whether it is a number, a document or an image. The question above puts you on the second kind, and that single fact removes one of the three remedies outright. It is worth stating in an interview before proposing anything, because "just increment it on the server" or "update that one field in place" is the most common answer to the overwritten change and it is only available on the first kind. ## Why read-modify-write is the only shape available When the server cannot interpret the value, a change to any part of it must be made by the caller: 1. Read the whole document. 2. Parse it, change the one field, serialise it again. 3. Write the whole document back. Two services doing that concurrently produce the classic outcome: both read the same version, both write a complete document, and the second write lands whole. Every edit the first writer made is gone — **including edits to fields the second writer never touched**, because it wrote the fields it read, at the values it read them. That last point surprises people: the two services can be working on entirely different parts of the document and one of them still loses everything. ## The remedies that survive | remedy | what it needs from the server | what it costs | what the loser does | |---|---|---|---| | server-side in-place update | must interpret the value | one round trip, no conflict | — (**not available here**) | | version token | must hand back a marker with the value and honour it on write | a wasted read-modify-write per lost attempt | re-reads and tries again | | conditional create | must create only if absent, atomically, and say whether it did | nothing extra, but only protects a first write | learns it did not create it | | expiring claim | conditional create, plus an entry lifetime | waiting, not wasted attempts | waits, or gives up | A note on the third row: a **conditional create** on its own is not a general fix for the overwritten change. It protects exactly one case — the first write of an entry that must be created once — and the two services above are updating an entry that already exists. Its real role here is as the primitive underneath the claim. ## Choosing between the two that remain - **Version token.** The caller reads the document and a marker together, computes the new document, and presents the marker with its write. The store refuses the write if the entry changed in between, so the change is never silently lost; the caller re-reads the current document, redoes its edit and tries again. This is the right default for a document that is updated occasionally by several services: conflicts are rare, and when they happen the cost is one extra round trip. Its mechanics and its retry convergence are the optimistic check-and-set leaf's subject; for this question you only need to know that it converts a silent loss into a refusal the caller can see. - **Expiring claim.** The caller creates a marker entry only if none exists, with an owner marker and a deadline, and runs its read-modify-write only if the creation succeeded. This serialises the callers rather than letting them race, which is what you want when the edit is expensive, when losing an attempt is costly, or when the cycle involves work outside the store. The price is that other callers wait, and that the deadline must outlast the work it guards — a claim that expires mid-work leaves two callers each convinced it is alone, which is the same race with more steps. The claim's lifecycle, renewal and safe release belong to the locks-and-leases leaf. ## The answer that is not a remedy Where many writers rewrite the same document at a high rate, both remaining remedies degrade, and the honest answer is often to stop storing one large document under one entry: split the independently-updated parts so the writers stop contending at all. That is a design change rather than a concurrency remedy, and it is the one that scales. ## Where stores differ Do not carry a rule from one store into this answer. Three capabilities vary across this class, and each one changes which remedies exist: - **Whether the server interprets values at all** — some read and modify counters, map fields and set members; others store and return bytes and nothing else. - **Whether a version token is offered** — where it is not, the caller cannot declare what it read and is left with a claim. - **Whether a construct exists that applies several operations as one uninterleaved unit** — some stores offer none whatsoever, and their only multi-step atomicity is a version token or a short submitted program. State which capability you are assuming out loud, and your answer stays correct whichever store the interviewer has in mind.
- If the value were a structure the server understands, how would the choice change?A server-side in-place update becomes available and is usually the right answer: the change happens where the value lives, so there is no gap to protect and no conflict to retry. It stays one round trip however contended the entry is. The limit is that it can only express changes the server knows how to make — arithmetic, a field set, a member added — so a decision that depends on the value read still needs one of the other remedies.
- Does any of this help if the two services are updating two different entries that must stay consistent with each other?Not directly. A version token and a claim are per-entry, and a unit covering several entries is only possible where those entries live on the same node — where the keyspace is split, placement decides whether the unit exists at all. If the two entries can end up on different nodes, this tier cannot give you the guarantee, and the invariant has to move somewhere that can.
- The version token keeps refusing the write and the caller keeps retrying. When is that a problem?When the entry is contended enough that a caller can lose repeatedly: every attempt spends a read, a parse and a write and produces nothing. Bound the attempts, and decide explicitly what happens when the bound is hit — fail the request, fall back to a claim so the callers queue instead of racing, or split the document so the writers stop contending.
saying these in an interview costs you the question
- Proposes a server-side increment on a value the server cannot read
- Treats a read-modify-write as atomic because each write is
- Assumes every store in this class can edit part of a value
- Thinks edits to different fields cannot overwrite each other
- Uses a conditional create to protect an update, not a first write
- Holds a claim whose deadline is shorter than the work it guards