skip to content

What invalidates a cached derived value in a component framework, and how is its dependency set determined?

level: middleimportance: must knowfreq 66%

answer

  1. remembered inputs plus remembered result
  2. declared by hand versus recorded from reads
  3. dirty mark now, recompute later
  4. too narrow is stale, too unstable never hits
  5. a copy taken outside tracking is invisible

basics

~20 s

A cached derivation remembers its last inputs and result; a change to an input in its dependency set invalidates it. That set is either declared by hand or recorded from the body's reads. Too narrow serves stale values; unstable inputs never hit.

solid answer

~50 s

A cached derivation stores the inputs it last ran with plus the result, compares the current inputs against them on each request, and recomputes only on a mismatch — so what invalidates it is a change to something in its dependency set. That set arrives one of two ways: declared by hand next to the computation, which nothing verifies against the body and so can be wrong or can rot; or tracked at read time, where the runtime records every reactive value the body actually read, which cannot disagree with the body but only sees reads going through the tracked source. Invalidation marks the cache dirty; recomputation then happens eagerly with the write or lazily on the next read. Two failure modes follow: a set too narrow serves a stale result, and an input rebuilt on every request means the cache never hits.

go deeper

for a junior

Recall that a cached derivation remembers its last inputs and its last result, and recomputes only when an input in its dependency set differs.

for a middle

Explain both ways the dependency set is obtained — declared by hand or recorded from the reads the body performs — and what each gets wrong.

for a senior

Diagnose from symptoms: a stale result points at a set that is too narrow, a constant recompute at an input rebuilt on each call. Confirm by counting, not reasoning.

for a principal

Weigh the maintenance side: hand-declared sets rot as bodies change, so where the tooling records them the risk profile of caching across a codebase is different.

A **cached derivation** is a computed value that remembers the inputs it last ran with and the result it produced. Asked for the value again, it compares the current inputs against the remembered ones; if they match it returns the stored result, and if they do not it recomputes and stores the new pair. Everything that is interesting about such a cache follows from one question: **what counts as an input?** That set is the dependency set, and it is the whole correctness story. ## Where the dependency set comes from Frameworks obtain it in one of two ways, and the failure modes differ: - **Declared by hand.** You list the values the computation depends on alongside it. The runtime compares the listed values between evaluations and recomputes when one differs. The list is a claim you make, and nothing checks it against the body of the computation, so it can be wrong — or can rot when someone edits the body later. - **Tracked at read time.** The runtime runs the computation once with recording turned on and notes every reactive value it actually read. Those reads become the dependency set, recorded fresh on each recomputation. The set cannot disagree with the body, because the body produced it. Tracking is not magic. It records reads that go **through the tracked source**. A value pulled out of tracking beforehand — copied into a plain local, captured earlier, read through something the runtime does not observe — is invisible to the recorder, and a change to it will not invalidate anything. Conditional bodies also narrow the set honestly: a branch that did not run this time contributed no reads, so a value only read in the untaken branch is not a dependency until the branch runs. ## What invalidation actually means Invalidation is not the same as recomputation: 1. A write to a dependency **marks the cache dirty** — the stored result is no longer trusted. 2. Depending on the design, the runtime either recomputes **eagerly** as part of processing the write, or **lazily** on the next read of the derived value. 3. A lazy derivation that nothing reads may never recompute at all, which is a feature: unwatched work is skipped. Batching sits on top of this: several writes in one turn typically produce one dirty mark and one recomputation, not one per write. ## The two ways it goes wrong | Symptom | Dependency set | Cause | Fix | |---|---|---|---| | Serves an old result after state changed | too narrow | an input the body reads is missing from the set, or was read outside tracking | add it to the set, or read it through the tracked source | | Recomputes on every request | too broad or unstable | an input is rebuilt fresh each time, so the comparison never matches | derive from the stable underlying values, or compare the parts that matter | The second row is the more common and the less noticed, because nothing is visibly broken: the value is always correct, the cache simply never hits, and the code now pays the computation **plus** a comparison plus the memory of a result it never reuses. A useless cache is a cost with no benefit and it looks exactly like a working one. ## Diagnosing either symptom - For a stale value, list what the body reads and compare that list with the dependency set, line by line. Look especially for values that were captured or copied before the computation ran. - For a cache that never hits, look at what the inputs *are* rather than what they mean: a collection or object created inside the caller, a value assembled from fields each time, a default constructed on the spot. Those change identity on every request even when nothing about the data changed. - Check the timing, too. If recomputation is lazy, the work appears at the moment of a read, which can be a surprising place, and the value can look "late" without being wrong. ## What to say in an interview Two sentences carry most of the credit. First: a cached derivation is a remembered pair of inputs and result, and the dependency set decides when the pair is thrown away. Second: get the set too narrow and you serve a stale value; get it too broad or let an input be rebuilt each time and the cache never hits, which costs more than not caching. Adding that hand-declared sets can drift from the body while tracked sets cannot — and that tracking only sees reads that pass through the tracked source — shows you know the mechanism rather than the incantation.

  • Why can a hand-declared dependency set be wrong when a tracked one cannot?
    A declared set is a separate claim about the body, and nothing compares the two, so it can omit a value from the start or fall out of step when the body is edited later. A tracked set is produced by running the body with recording on, so it is whatever the computation actually read on that pass — it is derived from the code rather than asserted about it.
  • What is the difference between invalidating a cached derivation and recomputing it?
    Invalidation marks the stored result untrusted; recomputation produces a new one. A design may recompute eagerly while processing the write, or lazily on the next read — and a lazy derivation nothing reads may never recompute at all, which is deliberate: unwatched work is skipped. Batching usually collapses several writes in one turn into a single dirty mark and a single recomputation.
  • Why is a cache that never hits worse than no cache at all?
    It pays the full computation every time, plus the input comparison, plus memory for a result nothing reuses. Nothing looks broken because the value is always correct, which is why these survive for years. Confirming a cache actually hits — a counter inside the expensive step is enough — is worth more than reasoning about whether it should.

saying these in an interview costs you the question

  • Thinks a cached derivation refreshes whenever the component renders
  • Believes listing more dependencies is always the safer choice
  • Cannot explain why an input rebuilt each call defeats the cache
  • Assumes automatic tracking sees values copied out before the computation ran
  • Treats a cache that never hits as harmless because the value is still correct