What must an adapter that bridges a foreign store into a component tree guarantee about its snapshot read?
answer
- two functions: subscribe and read
- synchronous read, no waiting
- same identity until a write
- hold the snapshot, never build it
basics
~20 sThe read must be synchronous, return the same identity until a write happens, and return a new one after a write. Otherwise the runtime either sees a change on every check and never settles, or never sees one at all.
solid answer
~50 sBridging means handing the runtime two functions: a subscribe that registers a listener and returns a release, and a **snapshot read** that returns the current value. The read carries the hard constraints. It must be **synchronous**, because a render has to produce output now. Its result must keep the **same identity between writes** and take a **new identity after one** — the runtime compares snapshots by identity, so a read that builds its result looks changed on every check and schedules work that never settles, while a snapshot mutated in place looks unchanged and hides real writes. One update pass must also see **one consistent version**, which matters for a runtime that can pause a pass part-way and read the same store twice; disagreeing reads mean part of the output describes the old state. Practically: build the snapshot when a write lands, hold it, return the held reference, and re-read straight after subscribing.
go deeper
Recall the two functions a bridge supplies: a subscribe that returns a release, and a synchronous read of the current value. Remember the read must not wait for anything.
Explain why identity matters in both directions: the same identity between writes so checks are no-ops, a new identity after a write so the change is visible. Know why the snapshot is held rather than rebuilt.
Show the production symptoms and their causes: work that never settles from an allocating read, invisible writes from an in-place mutation, and a missed write from the gap between the first read and the subscription.
Decide who owns bridges in the codebase. One reviewed adapter per foreign store, with the guarantees written down, beats an ad-hoc bridge per feature — especially where a runtime may pause an update part-way and read twice.
## Two kinds of store, one of which needs no bridge A store built on the framework's **own** reactive primitives is already inside the runtime's bookkeeping: reading it from a component establishes whatever dependency that runtime uses, and the release rides along with the instance. There is nothing to adapt. A **foreign** store is any state container with its own notification channel that the framework knows nothing about — an object with a hand-written listener list, a container shared with code outside the tree, a wrapper over a long-lived connection. Connecting it to the tree means handing the runtime two functions, and the interesting part is what those functions must promise. ## The adapter - **subscribe(listener) returns release** — registers a listener called after each write, and returns a function that removes it. The listener needs no payload; it says only "re-read". - **read() returns a snapshot** — returns the current value, both for rendering and for the runtime to compare against what it last saw. That is the entire surface. Everything below is a constraint on the read. ## The guarantees | Guarantee | Why it exists | What breaks without it | |---|---|---| | **Synchronous** | a render must produce output now | nothing to render, and no version to compare | | **Same identity between writes** | the runtime compares snapshots by identity | every check looks like a change; scheduled work never settles | | **New identity after a write** | identity change is the only signal | real writes are invisible and the view never updates | | **One version within one update pass** | the runtime may read more than once per pass | part of the output describes the old state | | **Works with no subscriber** | a server render never subscribes | the first render has nothing to read | ## The two mirror-image failures **Allocating on read.** If the read builds its result — maps a collection, assembles an object, computes a field — it hands back a new identity every call. A runtime typically re-reads after committing, to check whether the store moved while it was working; a freshly built result never compares equal, so it schedules again, reads again, and never converges. The fix is to hold the snapshot: build it when a write happens, and return the held reference on every read until the next write replaces it. **Mutating in place.** The mirror image is a read that returns the one object the store keeps editing. Identity never changes, the comparison always says no-op, and genuine writes are invisible. A snapshot must be *replaced*, never edited. Those two failures are why the guarantee is a pair, not a single rule: the same identity until a write, a different identity after one. ## Consistency within one pass The subtlest guarantee is that a single update pass sees one version of the store throughout. A runtime that renders a pass straight through reads once and cannot notice a difference. A runtime that can **pause an update part-way and resume it later** may read the same store at two moments inside what the user experiences as one update; if a write lands between those reads, part of the output describes the old value and part the new — two components on screen disagreeing about the same state. The pass-level machinery that detects that and restarts the work belongs to the scheduler; it can only do its job if the adapter's read is synchronous and identity-stable. An adapter that recomputes on every read gives the scheduler nothing it can compare, so the check silently degrades into a loop. ## The subscribe gap, and the release Between the render's read and the listener's registration there is a window, and a write inside it notifies nobody. The adapter must therefore **re-read after subscribing** and report a change if the value moved. Symmetrically, the release must actually remove the listener from the store's list rather than flagging it inert: that list is the reference outliving the component, and a flagged listener still costs a call per write and still retains everything its closure captured. If the component is re-pointed at a different store instance, release the old subscription before taking the new one. ## Server rendering On a server there is nothing to subscribe to: markup is produced once and the work ends. The adapter needs a read that functions with no subscription at all. When the same tree will later render in the browser over that markup, the client's first read must see the data the server's read saw, which normally means the server's data travels with the response and seeds the client's instance. Otherwise the first client render disagrees with the delivered markup. ## A checklist 1. Hold the snapshot; never build it inside the read. 2. Replace the snapshot on write; never mutate it. 3. Keep the read synchronous and cheap. 4. Re-read immediately after subscribing. 5. Release on teardown and when the store instance changes. 6. Provide a read path that works with no subscriber.
- Why does a snapshot built on every read cause work that never settles rather than one extra render?The runtime re-reads after committing, to check whether the store changed while it was working. A freshly built result never compares equal to the one it saw, so it treats that as a change, schedules again, reads again and repeats. Holding the snapshot and returning the held reference until the next write makes the comparison meaningful.
- What does the adapter owe a server render, where nothing ever subscribes?A read that works with no listener registered, since markup is produced once and the work ends. If the same tree renders again in the browser over that markup, the client's first read must see the same data, so the server's data travels with the response and seeds the client's own store instance.
- Why is a snapshot that the store mutates in place worse than a snapshot rebuilt each read?Both break the comparison, in opposite directions. A rebuilt snapshot looks changed constantly, which is loud and gets fixed. A mutated one looks unchanged forever, so genuine writes never reach the view and the bug presents as "the value is right in the store but wrong on screen", which is far harder to trace.
saying these in an interview costs you the question
- Builds the snapshot by mapping the store's data on every read.
- Returns a promise from the read and awaits it while rendering.
- Mutates the snapshot object in place, so no change is detected.
- Assumes writes during the subscribe gap are delivered later anyway.
- Skips the adapter because the store is 'just a plain object'.