skip to content

How does a renderer's per-node work differ when it creates a first mount, updates a live tree, or claims existing markup?

level: middleimportance: must knowfreq 58%

answer

  1. construct, write-the-delta, or adopt
  2. claiming skips writes, not the compute pass
  3. behaviour is what markup cannot carry
  4. structure mismatch forces a rebuild

basics

~20 s

Create builds and inserts every node and writes every property. Update mutates only what differs, plus moves and removals. Claim-existing walks markup produced elsewhere, reuses those nodes untouched, and only attaches listeners, references and instance state.

solid answer

~50 s

A renderer runs the same description through three modes. In **create** mode nothing exists: it constructs each host node, writes every attribute and text value, inserts it before the right sibling, and wires listeners. In **update** mode it already holds a node reference per position, so it writes only the properties whose values differ, reorders keyed children, and removes and tears down what disappeared. In **claim-existing** mode the nodes are already in the host — produced by a server render or a prerender — so it walks them in lockstep with the description, assumes the markup is correct and skips the property writes, and spends its time attaching listeners, populating references and installing instance state. The third mode is the fragile one: it has to decide what to do when the node it finds does not match what it expected.

go deeper

for a junior

Know the three situations a renderer can be in: nothing exists yet, a tree it already built exists, or markup someone else produced exists. The work it does is different in each.

for a middle

Be able to say what is skipped in each mode and what is not — adopting nodes skips construction and property writes but never skips running the output code or attaching behaviour.

for a senior

Show that you profile the claim pass on its own, and explain the cost of a structural mismatch: the region is rebuilt after paint, wasting the work done upstream and shifting layout.

for a principal

Reason about which regions deserve to be claimed at all. Markup that is cheap to produce but expensive to wire may be better rendered in the browser than adopted.

## One description, three modes A renderer's job is to make the host agree with a description. What that costs depends entirely on what is already there. - **Create** (first mount of a tree or subtree): nothing exists. Every node is constructed, every property written, every node inserted, every listener wired. - **Update** (a tree the runtime already committed): the runtime holds, per position, both the previous description and the concrete host node. It writes the delta only. - **Claim existing** (markup produced elsewhere — a server response, a prerendered file, a cached shell): the nodes exist but the runtime knows nothing about them. It walks them alongside the description, adopts each one as the node for that position, and adds the behaviour the markup cannot carry. ## What each mode actually does per node | Work | Create | Update | Claim existing | |---|---|---|---| | Construct the host node | yes | only for added nodes | no — adopt what is there | | Write attributes, styles, text | all of them | only differing values | skipped; markup is assumed correct | | Insert into the tree | yes | only for added or moved nodes | no | | Attach listeners | yes | only where handlers appeared | yes — this is the bulk of the work | | Populate references, install instance state | yes | keep the existing ones | yes | | Teardown path | none | for removed subtrees | none on the first pass | | Typical failure | none — the tree is authoritative | a stale write or a lost move | a node that does not match the description | ## Why claiming is cheaper, but not cheap Claiming skips node construction and property writes, which is real savings on a large document. It does **not** skip the compute half: the output-producing code still runs in the browser to know what the tree *should* be, instance state still has to be built, and listeners still have to be attached one way or another. That is why a page can be fully painted and still unresponsive — the visible part came free from the markup, the behaviour did not. It also inherits a hard requirement: the client's **first** described output must line up with the markup node for node. In create mode there is nothing to disagree with; in update mode the runtime itself wrote the previous state, so it knows it. In claim mode the previous state was produced by another process, and the runtime is trusting it. ## When the node found does not match A renderer has two responses, and frameworks pick per case: 1. **Patch it** — keep the node, write the value the client computed, and carry on. Cheap, and adequate when only text or an attribute differs. 2. **Discard and rebuild** — drop the node or the whole subtree and re-run in create mode for that region. Necessary when the *structure* differs, because walking in lockstep is no longer possible. The second is where the cost shows: the server's work for that region is thrown away, and if the mismatch sits near the root, a large part of the page is rebuilt after it was already painted — visible as a flash or a layout shift. ## Modes are per subtree, not per app All three often run in one page's lifetime and even in one pass: - The shell of a page is claimed; a widget that only exists in the browser is created; a later user interaction updates both. - A region can be claimed later than the rest, so an interactive area is wired before the whole document is. - A subtree discarded because of a mismatch switches from claim to create mid-pass. ## Where frameworks differ A runtime that re-runs the output-producing function treats all three modes as variants of the same tree walk, with a flag that says *adopt instead of construct*. A fine-grained runtime has less to do in update mode by construction — each dynamic value already knows its node — but has to locate those nodes when claiming, which is why such runtimes often emit position markers into the markup. A compile-time runtime can generate three specialised code paths, one per mode, and usually produces the smallest claim pass of the three. In every case the conceptual distinction is the same: construct, write-the-delta, or adopt. ## Practical implications - Measure the claim pass separately from paint; they are different problems with different fixes. - Treat a structural mismatch as a bug in the description, not as noise to silence. - Remember that adopting a node does not adopt everything: whatever the markup cannot express — instance state, listeners, references, values a property holds rather than an attribute — is still the client's job on that first pass.

  • Can one pass use more than one of the three modes?
    Yes, routinely. A page's shell can be claimed while a browser-only widget inside it is created, and a subtree whose markup did not match the description is discarded and re-run in create mode mid-pass. Later interactions then drive the same tree in update mode. The mode is a property of a subtree at a moment, not a setting for the application.
  • What state does adopting an existing node fail to bring with it?
    Everything the markup cannot express: component instance state, listeners, reference slots, values that live as a node property rather than as a serialised attribute, and anything the upstream render deliberately omitted. That is exactly the list the claim pass has to install, and why the pass costs script time even though no node is constructed.
  • Why is a structural mismatch more expensive than a differing text value?
    A differing value can be patched in place: write the client's value and continue walking. A differing structure breaks the lockstep walk, because the runtime can no longer tell which existing node belongs to which description. Its only safe move is to discard that subtree and rebuild it in create mode, discarding the upstream work and often shifting layout after paint.

saying these in an interview costs you the question

  • Thinks claiming existing markup skips running the component code
  • Believes an update rewrites every attribute of the nodes it keeps
  • Says claimed markup is discarded and rebuilt as a matter of course
  • Assumes painted markup means the page is interactive
  • Treats the three modes as whole-app settings rather than per subtree