skip to content

In a runtime with read-time dependency tracking, what makes a read of a reactive value create a subscription?

level: middleimportance: should knowfreq 60%

answer

  1. the graph is recorded, not declared
  2. something must be currently running
  3. read through the handle, inside the scope
  4. re-collected per run, so branches matter

basics

~20 s

The read must go through the value's handle while a tracked computation is running. The runtime knows which computation is currently executing and records an edge to it; a read with no such computation active records nothing.

solid answer

~50 s

Tracking is a run-time recording, not static analysis. The runtime keeps a notion of "the computation currently running": when a tracked computation starts, it is pushed as the current one, and every read through a reactive handle while it is current records an edge in both directions - the value gains a dependent, the computation gains a dependency. Two conditions must both hold. First the read goes through the handle, so runtime code actually observes it. Second some tracked computation is active, which is why a read from an event handler or module top level, or in a continuation that resumes after the tracked run has already finished, registers nothing. Because dependencies are re-collected on each run, an untaken branch creates no edge and stale edges are dropped, so the graph follows the code path actually executed.

go deeper

for a junior

Remember the shape: reading a reactive value inside something the framework re-runs is what subscribes you. Reading it in an ordinary callback just gives you the value.

for a middle

Describe the push-run-record-pop sequence and both conditions - read through the handle, with a tracked computation current. Then explain why dependencies are re-collected per run.

for a senior

Show the failure you have diagnosed: an effect that never re-ran because its read happened after an asynchronous pause, or one woken constantly because a read that should have been untracked was not.

for a principal

The graph is implicit, which is its strength and its review problem. Decide how your codebase makes dependencies legible - conventions about untracked reads, and about which layers are allowed to hold tracked handles at all.

## Tracking is a recording, not an analysis In a fine-grained runtime, the dependency graph is not declared and not inferred from source. It is **recorded while the program runs**, by a mechanism simple enough to describe in two sentences: the runtime holds a reference to the computation that is currently executing, and every read through a reactive handle asks "is anything current?" If something is, an edge is written between that value and that computation. A tracked computation is anything the runtime runs on your behalf and may re-run: a derived value, an effect, or the reactive expression behind a binding. Running one looks like this: 1. push this computation as **current**, and clear the dependencies it collected last time; 2. run its body; 3. every handle read during the body records the value as a dependency of the computation, and the computation as a dependent of the value; 4. pop; whatever edges were not re-recorded this run are released. ## The two conditions, both required - **The read goes through the handle.** Runtime code has to be on the path. A value copied into a plain local before the computation ran, or read through anything that bypasses the accessor, is just data by the time the computation sees it. - **A tracked computation is current.** Reading the same handle where nothing is running is a perfectly legal read that returns the value and records nothing, because there is no dependent to record. That second condition explains most surprises: | Where the read happens | Edge recorded? | Why | |---|---|---| | Inside a derived value or effect body | yes | that computation is current | | Inside an event handler | no | handlers are not tracked computations | | At module or setup top level, outside any computation | no | nothing is current | | In a continuation that resumes after an asynchronous pause | no | the tracked run already finished and popped | | Inside a nested tracked computation | yes, to the inner one | the innermost current computation owns the read | ## Dependencies are dynamic, and that is the point Because collection happens per run, the graph mirrors the path actually taken. A computation shaped like "if the flag is on, read the expensive list; otherwise read the placeholder" depends on the flag plus whichever branch ran. Turn the flag off and the next run records no edge to the list, so writes to the list stop waking it. No configuration expresses that; it falls out of the recording. The corollary is that a computation can depend on different values from run to run, and the runtime must therefore **release** edges as well as add them. Most runtimes re-collect from scratch per run precisely so stale edges cannot accumulate, and release everything on teardown so a destroyed computation is not kept alive by the values it once read. ## Reading without subscribing Because the mechanism is "whatever is current", runtimes expose a way to read deliberately *without* recording: run a snippet with no current computation, so reads inside it are untracked. This is how you consult a value you do not want to be woken by - a counter you increment, a configuration you read once for a log line - inside a computation that tracks other things. The mirror image also exists: running a piece of work explicitly *inside* a tracked scope so that its reads do count. ## Why the ordering question follows immediately Once a write can wake many dependents, the runtime must decide in what order they run. If a derived value and a binding that reads it are both dependents of the same source, running the binding first would show it a derived value computed from the old source. Fine-grained runtimes therefore order work along the graph, so a dependent runs after everything it depends on has settled. That the graph exists at all is what makes this solvable - it is the same recording, used for ordering instead of for waking. ## Contrast with the other models The other three models answer "who depends on this?" differently, which is worth one sentence each. A re-run-and-diff runtime does not answer it: it knows only which component's slot was written and re-runs that function. A compile-time model answers it **statically**, from source, which is why it needs the read and the write to be in a shape it can analyse. A dirty-checking runtime never asks, and instead compares bindings' current values with last-rendered ones. Read-time tracking is the only one of the four whose dependency answer is exact **and** discovered at run time, which is what lets it wake a single binding. ## What to say in an interview "Reading through the handle while a tracked computation is current is what creates the edge - both conditions matter. Dependencies are re-collected on every run, so the graph follows the branch that actually executed, and reads from untracked places such as an event handler or a resumed continuation deliberately record nothing."

  • Why do fine-grained runtimes re-collect a computation's dependencies on every run instead of keeping the first set?
    Because a computation's reads depend on the path it takes. Keeping the first set would leave a computation woken by values a later run no longer reads, and blind to values a new branch does read. Re-collecting also releases edges, so torn-down computations do not linger in the graph.
  • How would you read a reactive value inside an effect without that effect re-running when it changes?
    Read it in an untracked scope - the runtime offers a way to run a snippet with no computation current, so reads inside record no edge. It is the standard tool for consulting a value you want the current data of but no dependency on, and it is easy to misuse into a stale read.
  • A tracked computation reads a value only after an asynchronous pause resumes. What has happened to the dependency?
    There is none. The tracked run ended when the body reached the pause, so nothing was current when the continuation read the handle. The fix is to read what you need before the pause, or to put the post-pause work in its own tracked computation.

saying these in an interview costs you the question

  • Thinks the dependency graph is derived from source code at build time.
  • Believes every read of a reactive value subscribes, wherever it happens.
  • Says dependencies are fixed after the first run of a computation.
  • Assumes an untaken branch still creates a dependency on what it would read.
  • Cannot say what "currently running computation" means for tracking.