A chat server replaces the whole room roster on each join instead of mutating it — what does that cost?
answer
- copying is not free
- cost scales with roster size
- roster size times change rate
- two writers, one lost join
- sharing copies the changed path only
basics
~20 sEach join builds a successor to an n-entry roster, so the style pays copying and allocation proportional to roster size times change rate, it still needs one agreed writer, and readers work from the version they captured.
solid answer
~40 sThree costs, and they are easy to underestimate. **Copying**: building the successor of a roster with `n` entries copies those `n` entries unless the structure shares its untouched parts, so the bill is roster size multiplied by change rate. **Allocation**: every change leaves a new object behind and an old one to collect, so heavy churn shows up as memory traffic rather than as threads waiting. **A residual writer problem**: two joins that each read the same old roster and each publish their own successor lose one participant, so there must still be one agreed way to install the next value — wholesale replacement frees readers, not writers. Readers, meanwhile, work from the version they captured, which is consistent but may be slightly behind.
code
pseudocode · 4 linesfunction join(room, user)
old = room.roster // an immutable list of n entries
next = old + [user] // copies n entries, allocates a new list
room.roster = next // one publication; readers hold old or nextgo deeper
Remember that building a new roster is real work proportional to how many entries it copies, and that the old one still has to be collected afterwards.
Explain the cost as roster size times change rate, and say what structural sharing changes about that product and what it charges readers in return.
Demonstrate the writer trap: two joins from one base losing a participant, and the publication rule that prevents it without reintroducing a guard on every read.
Set the boundary for a codebase: which state may be published as values, which must stay current at the point of use, and what measurement moves a region across that line.
Replacing a value wholesale is the move that makes the immutable style work under concurrency: nothing that a reader holds is ever written, so no reader needs permission from anyone. The interesting engineering is in what that move costs, because the costs are real and they scale differently from the ones it removes. ## The copy, and what `n` is Let `n` be the number of participants on the roster and `c` the number of changes per second (joins plus leaves). Building the successor of a flat list copies all `n` entries, so the style's steady-state work is proportional to `n x c`. That product, not either factor alone, is the number to reason about: - A room with 8 participants that churns constantly: `n` is tiny, copying is invisible. - A room with 50,000 participants that changes twice an hour: `c` is tiny, copying is invisible. - A room with 50,000 participants where people join and leave every second: the product is large, and copying now dominates everything else the server does for that room. The important refinement is that `n` entries is the cost of a **flat** structure. A **persistent structure with structural sharing** — a tree whose unchanged subtrees are reused by the successor — copies only the nodes along one path, roughly proportional to the depth rather than to `n`, and shares the rest. That changes the arithmetic from `n x c` to something much flatter, at the price of a slower walk and more indirection on every read. Choosing the representation is therefore part of choosing the style, not a detail below it. ## Allocation is a different cost from waiting The guarded style's cost is *time spent waiting*, which appears as latency on the threads that wanted the state. The value style's cost is *objects created and discarded*, which appears somewhere else entirely: in memory traffic, in collection pauses, in cache pressure as the working set stops fitting. Two teams can both be right that their style is faster and be measuring different things. The honest comparison names both: 1. Measure the guarded arrangement by how long accesses wait and how that grows with the read rate. 2. Measure the value arrangement by bytes allocated per change and how that grows with roster size. 3. Compare them at the *busy* rates, not the average ones — both styles are fine when the room is quiet. ## Wholesale replacement does not remove the writer's problem This is the part candidates most often skip. Consider two joins arriving together. Each reads the current roster, each builds a successor containing its own new participant, each installs it. One of the two participants is simply gone — not corrupted, not half-written, gone, because the second publication overwrote a value built from a stale base. An immutable value guarantees that nobody observes a half-built roster; it guarantees nothing about which of two successors survives. So the style still needs exactly one agreed way to move `current` forward — an owner, or a publication that refuses when the base has moved. ## Staleness, and why it is usually fine here A delivery loop that captured the roster before a join completes will finish its fan-out against the captured version. A participant who joined during the fan-out misses that message; one who left may still receive it. The window is as long as one fan-out. For a chat room this is ordinarily invisible and acceptable. For a state where the answer must be current at the moment it is used — a limit check, an authorisation decision — it is not, and that is the signal to keep the state guarded instead of published. ## Where the style stops paying | Condition | Effect on the value style | |---|---| | Roster small, change rate low | Copying is free in practice; the style wins outright | | Reads heavily outnumber changes | Best case: cost lands on the rare event | | Roster large and churning | Copy cost times change rate becomes the bottleneck | | Readers must see the newest change | Captured versions are the wrong tool; guard instead | | Structure shares unchanged parts | Copy cost falls to the changed path, and the style stretches much further | The summary an interviewer is listening for: wholesale replacement buys coordination-free reading and pays for it in copying, allocation and a bounded staleness window, while leaving the writers to agree among themselves. It is a trade, sized by two numbers you can actually measure, not a free upgrade.
- What changes if the roster is a tree-shaped persistent structure instead of a flat list?The successor reuses every untouched subtree and copies only the nodes on one path, so the per-change cost tracks the depth rather than the participant count. Large, churning rooms become affordable. You pay for it on every read, in extra indirection while walking the structure.
- How would you decide, with measurements, that this style has stopped paying?Watch bytes allocated per second attributable to roster changes and the fraction of time spent collecting them, against the waiting time the guarded alternative would incur at the observed read rate. When allocation cost exceeds that waiting, the trade has inverted.
saying these in an interview costs you the question
- Says immutable structures never copy anything at all
- Thinks replacing the whole value removes the need for one agreed writer
- Judges the style on roster size while ignoring the change rate
- Assumes a fan-out in flight picks up the newest roster
- Believes allocation is free because the old value becomes unreachable