skip to content

A state write reveals a panel and the next line measures it and finds nothing - why, and what is the correct fix?

level: seniorimportance: should knowfreq 58%

answer

  1. the write only queued work
  2. nothing has been applied yet
  3. read after the flush, not after the write
  4. after-update callback or flush signal
  5. a synchronous flush costs an extra pass

basics

~20 s

The write only queued an update, so the host tree still holds the previous output when the next line runs. Move the read into the runtime's after-update signal, or force a synchronous flush for that one read.

solid answer

~40 s

Writing state does not build output; it marks work and returns. The panel's nodes appear when the queue is flushed, which happens after the handler's synchronous code finishes, so a measurement on the next line inspects the tree as it was before the write. There are three honest fixes, in order of preference: do the read in the callback the runtime runs after it has applied the update; await the signal the runtime resolves when its queue has drained and read there; or, when the value is genuinely needed before the user could see an inconsistent frame, wrap the write in the synchronous-flush escape hatch, accept the extra pass, and keep it out of high-frequency handlers. The fix to avoid is a timer with a guessed delay.

go deeper

for a junior

Remember the order: the state write comes first and the output is applied later. Anything that needs finished output has to run after the update, not on the next line.

for a middle

Explain the queue-then-flush sequence and name the two correct places for the read: the after-update callback, or the signal the runtime resolves once its queue has drained.

for a senior

Diagnose it instead of guessing: prove the read happens before the flush, then choose the fix that fits the runtime and justify any synchronous flush by the frame the user would otherwise see.

for a principal

Set the boundary: measurement-driven positioning is a known hazard, so give it one reviewed helper rather than letting timers and synchronous flushes spread through feature code.

This is the single most common bug that comes out of scheduling, and it has one cause: **the code reads output that has not been produced yet.** ## What the write did, and what it did not do A state write marks the owning instance or the dependent computations as dirty, makes sure a flush is scheduled, and returns. It does not run the output function, it does not reconcile, and it does not touch host nodes. All of that happens in the flush, which the runtime schedules for after the event's synchronous work - on a microtask, on its own next tick, or before the next frame. The handler therefore continues on top of the **previous** committed output. A measurement taken there sees a panel that is still absent, a list that is still the old length, or a node whose size reflects the state before the write. Notice that this is independent of the write model. In a runtime with fine-grained cells the *value* is new on the next line, which makes the trap easier to fall into: the value agrees with you and the tree does not. ## The three correct fixes | fix | when the read runs | what it costs | |---|---|---| | the runtime's after-update lifecycle callback | once this pass has been applied | nothing; it is the intended callback | | await the runtime's flush-complete signal, then read | once the queue has drained | one continuation, and the read leaves the handler | | synchronous-flush escape hatch around the write | immediately, inside the handler | this event loses coalescing and pays for an extra pass | Prefer them in that order. The first two are free and say what they mean. The third is a real tool with a real price, and it is the right call in a narrow case: the measured value is needed before the user could see an inconsistent frame - positioning a freshly revealed element against an anchor, or restoring a scroll offset within the same interaction. Used once, deliberately, it is fine. Used habitually it converts the runtime's one-pass-per-event guarantee into one pass per write, and inside a handler that fires continuously while a pointer moves, it is a classic stutter cause. ## The fixes that look like fixes 1. **A short timer.** It guesses. A delay that works on a development machine can be shorter than the flush on a slower device or under a heavier pass, so the bug returns later as a rare, load-dependent failure that no one can reproduce. The runtime already tells you when its queue has drained; prefer the signal to a number. 2. **Polling until the node appears.** It burns the main thread to discover something the runtime would have told you, and it still needs a timeout, which is the guess again. 3. **Reading inside the output function.** That code runs *before* anything is applied. It sees, at best, the previous commit - and in a runtime that may abandon a half-done pass, a tree that never becomes visible at all. It also makes the function impure, which is a second bug layered on the first. 4. **Reading state back instead of the tree.** It answers a different question. Knowing the new value tells you nothing about whether nodes exist, and in a snapshot runtime the read-back gives the old value anyway. ## How to diagnose it rather than guess Prove the ordering before you change anything: - Log at the write, at the read, and in the after-update callback. If the read logs before the after-update line, it ran before the flush and the diagnosis is settled. - Check whether the read is correct on the *second* interaction. A measurement that is wrong once and then right is reading the previous pass, which is this bug's signature. - Ask whether the value must exist synchronously. Most of the time the answer is no, and the after-update callback ends the discussion. ## Why the runtime is designed this way Deferring the work is what lets writes coalesce, what keeps the screen consistent, and what lets a runtime reorder or abandon a pass. A runtime that applied output on every write would make this bug impossible and every other scheduling guarantee impossible with it. The contract is straightforward once stated plainly: *write state, then let the runtime tell you when the output exists.* Note that the cost of the measurement itself - what a layout read makes the host engine recompute - is a separate subject belonging to the platform, not to the framework's scheduler; the scheduling question is only **when** the node exists to be measured. ## The rule of thumb worth memorising - Write state, then wait to be told the output exists; never assume the next line sees it. - Default to the after-update signal, and let a synchronous flush be a commented exception. - Treat a guessed delay in this position as an unfixed bug rather than a fix. Anything that needs finished output - a measurement, focusing a newly revealed control, scrolling to a new row, handing a node to a non-framework library - belongs after the update, not after the write. Reach for the after-update signal by default, and let a synchronous flush be a deliberate, commented exception.

  • Why is a short timer a bad fix for this?
    It guesses. A delay that works locally can be shorter than the flush on a slower device or under a heavier pass, so the bug returns as a rare, load-dependent failure. The runtime already exposes a signal for when its queue has drained; using a number instead means the correctness of the code depends on machine speed.
  • When is the synchronous-flush escape hatch actually the right call?
    When the measured value must exist before the user could see an inconsistent frame - positioning freshly revealed output against an anchor, or restoring a scroll offset inside one interaction. It is a targeted tool: one call around one write, never the default, and never inside a handler that fires on every pointer move.
  • The read is moved into the function that produces the output, and it sometimes returns values from the previous pass. Why?
    Because that function runs before anything is applied; it describes output rather than observing it. It sees the previous commit at best, and in a runtime that can abandon a pass, a tree that never becomes visible. It also makes the function impure, so the runtime's freedom to re-run it now produces inconsistent reads.
  • Does this bug behave differently in a runtime where the written value is visible immediately?
    The cause is identical but the trap is easier to hit. The value reads new on the next line, which suggests the update has happened, while the host nodes still reflect the previous pass. Immediate values and applied output are separate guarantees in every runtime that coalesces work.

saying these in an interview costs you the question

  • Adds a short timer and declares the bug fixed
  • Says the framework is slow at updating the host tree
  • Wraps every write in a synchronous flush to be safe
  • Measures inside the function that describes the output
  • Concludes the state write did not take effect
  • Polls in a loop until the node shows up