skip to content

When component state is assigned a new value, how does a UI framework find out that anything changed?

level: juniorimportance: must knowfreq 72%

answer

  1. an assignment is not an event
  2. intercept the write, or re-check later
  3. the write must pass through the runtime
  4. four models: re-run, track, compile, check

basics

~20 s

Nothing announces a bare assignment. Either the write goes through code the runtime owns - a setter, a tracked handle, or an assignment a compiler rewrote - or the runtime re-checks values later at a moment it was told about.

solid answer

~50 s

A framework cannot watch arbitrary memory, so a change has to become visible one of two ways: the write passes through the runtime, or the runtime looks again afterwards. That gives four models in wide use. **Re-run and diff**: you call a setter with a new value, the runtime marks that component and runs its function again. **Read-time dependency tracking**: reactive values are read and written through handles, so a read inside a computation records a dependency and a write notifies exactly those computations. **Compile-time instrumentation**: a build step recognises reactive declarations and rewrites each assignment into an assignment plus a targeted update. **Dirty checking**: nothing intercepts the write; at known points the runtime walks the tree comparing current values with last-rendered ones. Everything else about an update - how much re-runs, how narrow it is - follows from that one choice.

go deeper

for a junior

Be able to say that a plain assignment is invisible, and that the write therefore goes through the framework's own setter or handle - or the framework checks again later. Naming two of the four models is enough here.

for a middle

Name all four and describe what each one marks: a component subtree, one computation, one compiled binding, or whatever a walk visits. Then say what each asks of the developer in exchange.

for a senior

Tie the model to bugs you have actually debugged - a screen that did not refresh because the write bypassed the runtime, or one that refreshed far too widely because the marked unit was coarse.

for a principal

Frame the choice as a discipline the whole codebase inherits, not a benchmark. Every developer will pay the model's price on every feature, and the escape hatch it offers is where the hard failures collect.

## The problem: an assignment is invisible A UI framework's job is to keep what is on screen agreed with the values in memory. To do that it needs an answer to a question that sounds trivial and is not: **how does it learn that a value changed?** Putting a number in a field is not an event. Nothing in a general-purpose program announces it, and a framework cannot watch arbitrary memory. So frameworks arrange one of two things: - the **write passes through code the runtime owns**, so the moment of change is a call it can react to; or - the runtime **re-checks the values afterwards**, at a moment it has been told about. Four models built on that split are in wide use. They differ in what gets *discovered* and therefore in what gets *marked* to re-run. ## Model 1 - re-run and diff State lives in a slot the runtime holds for the component, and you change it by calling the runtime's setter with a **new value**. The setter records that this component is out of date and asks a scheduler to run the component's function again. The function re-executes top to bottom and returns a fresh description of what the component should look like. - **Discovered:** "this component's state was written" - nothing finer. - **Marked:** the component, and usually what it renders beneath it. - **Asks of you:** produce a *new* value rather than editing the old one in place, because the setter call is the signal. ## Model 2 - read-time dependency tracking Reactive values sit behind handles with a read path and a write path. While a tracked computation runs, the runtime remembers which computation is currently executing; every read through a handle records an edge between that value and that computation. A write then walks only the edges recorded for that value. - **Discovered:** exactly which computations read the value that was written. - **Marked:** those computations - possibly a single text binding deep in the tree. - **Asks of you:** read through the handle, inside a tracked computation, or no edge exists. ## Model 3 - compile-time instrumentation A compiler reads component source before it ships. It can see which declarations are reactive, which expressions in the template read them, and which statements assign to them, so it emits the update calls itself: an assignment becomes "assign, then mark these two bindings dirty." - **Discovered:** a specific assignment in specific compiled source. - **Marked:** the bindings the compiler proved depend on it. - **Asks of you:** write in a shape the compiler can analyse; anything it cannot see needs a run-time-tracked container instead. ## Model 4 - dirty checking Here nothing intercepts the write. At points the runtime knows about - an event handler returning, a timer firing, a request completing - it walks the tree, compares each binding's current value against the value last rendered, and updates where they differ. Because a check can itself change a value, the walk may repeat until it settles, usually with a cap on passes. - **Discovered:** a difference, after the fact, wherever the walk looks. - **Marked:** whatever the walk visits, narrowed in practice to subtrees the runtime was told about. - **Asks of you:** make sure the runtime is triggered - a write from an entry point it does not know about is simply not noticed until something else triggers a check. ## Side by side | Model | Change becomes visible when | Marked unit | Cost driver | |---|---|---|---| | Re-run and diff | the runtime's setter is called | a component subtree | size of the re-run subtree and its output | | Read-time tracking | a tracked handle is written | one computation | number of dependents | | Compile-time rewriting | a rewritten assignment runs | one compiled binding | dependents visible statically | | Dirty checking | the next check runs | whatever is walked | bindings checked, times passes | ## Two things this does not settle First, the models are not a ranking. Fine-grained tracking does the least wasted work per write; re-running a function is the easiest to reason about because a render is just a function of its inputs; a compiler moves the bookkeeping off the user's device; dirty checking asks nothing of the value itself, which is why it can sit on top of plain objects. Second, all four are about **discovery only**. Once work is marked, what happens to the host tree - whether a node is patched or replaced - and when the marked work is flushed are separate stages of the pipeline. A useful habit in an interview is to say the two apart explicitly: "the write told the runtime *what* is stale; a later stage decides *what that does* to the screen." ## Why interviewers open here Most update bugs a candidate has met - a screen that did not refresh, a screen that refreshed far too often - trace to the discovery step rather than to rendering. Someone who can name the model their framework uses can usually also say which discipline it demands of them, and that is the real signal being tested.

  • Why can a framework not simply watch a plain variable and react when it changes?
    Because plain variables have no notification channel. General-purpose runtimes offer no hook that fires when a field is assigned, and polling every reachable value is unaffordable. That is why each model either routes the write through the runtime or re-checks a bounded set of bindings at a known moment.
  • State is held in an object whose reads and writes both pass through an interception wrapper. Which model is that?
    Read-time dependency tracking. The wrapper is the handle: reads through it register the currently running computation as a dependent, writes through it notify the recorded dependents. The object shape is cosmetic - what matters is that both directions pass through runtime code.
  • Can one framework use more than one of these models?
    Yes, and several do. A compiler commonly rewrites what it can see statically and falls back to a run-time-tracked container for the rest; a re-run-and-diff runtime may still track fine-grained subscriptions for values held outside the tree. The models describe mechanisms, not mutually exclusive products.

A shop can learn its stock changed two ways: every sale rings through the till, or somebody counts the shelves at closing time. A framework faces the same choice - intercept the write, or re-check later.

saying these in an interview costs you the question

  • Thinks the framework watches plain variables and fires when one is assigned.
  • Says re-rendering is how the change is detected, rather than what follows detection.
  • Believes every framework discovers changes by comparing an old and new tree.
  • Cannot name any mechanism beyond "it re-renders when state changes".
  • Assumes the model is purely a performance choice with no discipline attached.