skip to content

A value owned near the root is threaded through five components that never read it, and every keystroke re-renders most of the page; how do you decide whether the owner sits too high?

level: seniorimportance: should knowfreq 52%

answer

  1. readers, not depth
  2. two costs: coupling and invalidation
  3. lowest ancestor containing every reader
  4. split volatile from committed
  5. count the pass-through levels

basics

~20 s

Decide from the readers, not the depth. List every component that reads or writes the value, take the lowest one containing them all, and compare it with the current owner; the levels between that only pass it along are the cost.

solid answer

~50 s

Two symptoms point at a wrong owner: **pass-through levels**, where a component receives the value only to hand it on, and **wide invalidation**, where a write re-runs far more of the tree than the change affects. Neither is proof on its own — depth is not the measure, the set of readers is. So enumerate the readers and writers, find their lowest common ancestor, and compare. If it sits far below the current owner, move ownership down and delete the pass-through inputs. If the readers really are spread across the page, the owner is correct and you attack the cost instead: split the state so the fast-changing part (the draft being typed) is owned locally and only the committed value is lifted, and narrow what the owner re-renders by keeping the unchanging regions out of its own output. The invalidation cost depends on the reactivity model; the coupling cost does not.

go deeper

for a junior

Notice the smell: a component that takes an input only to pass it to a child. Ask who actually reads the value before assuming it must live where it currently does.

for a middle

Explain the two distinct costs — pass-through coupling and how much a write invalidates — and derive the right owner by finding the lowest component that contains every reader.

for a senior

Show the diagnosis on a real page: enumerate readers, measure write frequency, then either move the owner or split volatile state from committed state, never duplicate it.

for a principal

Own the convention: decide where state may live by default, so ownership drift is caught in review rather than discovered as a performance incident, and accept that some values are legitimately page-wide.

## Two different complaints The scenario mixes two costs, and they have different fixes. 1. **Coupling.** Components on the path between the owner and the readers accept an input they do not use. Their signatures now advertise a dependency that is not theirs, they cannot be reused without it, and a reviewer reading one of them cannot tell whether it matters. 2. **Invalidation.** A write to the owner's state invalidates work. How much work depends on the runtime: where a write re-runs the owning component function and diffs its output, the unit of work is the owner's whole subtree, so a high owner changing on every keystroke is expensive; where reads are tracked individually, only the expressions that read the value re-run, and the same layout costs almost nothing. A third kind checks a marked subtree for changes, which again scales with where the mark sits. Knowing which you are in tells you whether the performance half of this problem exists at all. ## The diagnosis: readers, not depth Depth is a symptom, not the measure. A value legitimately read by a header, a sidebar and a footer belongs high, and that is not a defect. Work the question the other way: 1. List every component that **reads** the value and every one that **writes** it. 2. Find the lowest component of the tree that contains all of them. 3. Compare it with the current owner. The levels between the two are pure overhead. 4. Count how many components on the path pass the value without reading it — that number is the concrete cost you can put in a review. 5. Check the write frequency. A value that changes on every keystroke and one that changes at login have very different invalidation profiles at the same height. An honest outcome of this exercise is often "the owner is right and the value changes too often", which sends you to the second half. ## Fixes, by what the diagnosis found | Finding | Fix | What it costs | |---|---|---| | Readers all sit in one subtree | Move ownership down to that subtree's root; delete the pass-through inputs | A round of signature changes | | Readers are spread, value changes constantly | Split the state: own the volatile draft locally, lift only the committed value | Two cells, and a rule for when the draft commits | | Readers are spread, owner re-renders a big static region | Keep the unchanging regions out of the owner's own output — pass them in as content the owner places rather than builds | Some indirection in the composition | | Path components only forward the value | Have the ancestor make the value available to descendants without threading it | Removes the threading, not the invalidation; a different mechanism with its own trade-offs | The splitting row is the one candidates most often miss. "Every keystroke re-renders the page" is usually a sign that a per-keystroke value was lifted when only its *result* needed lifting. The field's current text can stay where it is typed; what the rest of the page needs is the submitted value, and that changes once. ## What not to do - **Do not keep a second copy lower down and sync it.** That converts an invalidation problem into a correctness problem: two cells for one concept, and drift when a write path forgets one. - **Do not memoise the path.** Wrapping every intermediate component in comparison checks can reduce re-rendering, but it leaves the coupling in place and adds comparisons on a path that should not exist. - **Do not treat one high owner as proof of a bad design.** Some values are page-wide by nature. The defect is a high owner with *few, clustered* readers. ## How to talk about it A strong answer names the measure before the remedy: ownership is chosen by the set of readers, so the fix follows from enumerating them rather than from a rule of thumb about depth. It then separates the two costs, because they are fixed differently — moving the owner addresses coupling, splitting volatile from committed state addresses invalidation, and only one of those two is even a problem in some runtimes. Finally, it says when to leave things alone: when the readers really do span the page and the value changes rarely, the owner near the root is the correct design and the pass-through inputs are the only thing worth cleaning up.

  • What does "split the state" mean for a search box whose term the whole page uses?
    The box owns the text being typed; the page owns the term it searches for. The committed value is lifted and changes once per submit or per debounce interval, while the per-keystroke text stays local. One concept became two with a defined relationship, which is different from two copies of one value.
  • Does a high owner cost the same in every reactivity model?
    No. Where a write re-runs the owning component and diffs its output, cost scales with the owner's subtree, so height matters a great deal. Where reads are tracked individually, only the expressions reading the value re-run and height is nearly free. The coupling cost — inputs threaded through uninterested components — is the same in both.
  • When is threading a value through several levels acceptable?
    When the path is short, the components on it are not meant to be reused elsewhere, and the alternative adds a mechanism the team would otherwise not need. Two levels of explicit passing is often clearer than an implicit channel; five levels of it is a signal to change either the owner or the mechanism.

saying these in an interview costs you the question

  • Judges the owner by how deep it is rather than by who reads the value
  • Adds a lower copy of the value and synchronises it with the owner
  • Memoises every component on the path and calls the coupling solved
  • Assumes every runtime re-renders the whole subtree of the owner
  • Lifts a per-keystroke value when only the committed result is needed elsewhere
  • Treats any state held near the root as automatically wrong