createMemo: Lazy Cached Computation & Dirty Version Propagation
- Last updated
- Prerequisites:
- createSignal and reactive reads
- Understanding of derived values vs effects
- solidjs
- createMemo
- memoization
- computed
- lazy
- caching
Read at your depth
The practical view
const double = createMemo(() => count() * 2) creates a cached derived value. Read double() anywhere to get the computed result; it recomputes lazily only when count() changed AND someone reads double(). Compare: a plain expression const double = count() * 2 in the component body computes once and never updates; an effect computes but isn't readable as a value. Memos are for expensive derivations used in multiple places — they compute once, cache, and update only when dependencies change. Derived signals (createComputed) are eager; createMemo is lazy.
The same idea in other frameworks
useMemo caches across renders but recomputes on every render unless deps change; Solid's memo recomputes only when a source changes and a read happens.computed() in Angular signals matches createMemo semantics (lazy, cached, dependency-tracked).Vue's computed() is equivalent — lazy cached getters with dependency tracking.Svelte 5 $derived compiles to a memo-like lazy computation.Legacy vs modern
Recomputing in the component body vs memoizing a derived value
A plain body computation runs once and is stale, or runs on every render; a memo recomputes only when its source changes and caches the result.
const doubled = count() * 2; // ❌ OLD: computes ONCE — never updates when count changesconst doubled = createMemo(() => count() * 2); // ✅ NEW: cached, lazy, updates on readInterview gotchas
Context
This separates developers who understand Solid's lazy-vs-eager computation models — interviewers want the scheduling semantics, not just 'memo caches values'.
The mechanical answer
createMemo is lazy: it does not run when a dependency changes; it marks itself dirty and only recomputes when something reads it. This makes it ideal for pure derived state that may not be consumed every cycle — the computation runs at most once per dependency change, and only if a read actually occurs. createComputed is eager: it re-runs as soon as a dependency changes (scheduled like an effect) and produces a value that is always current. Use memos for derivations you read in multiple places (cached selectors); use computed when you need a side-effect-free eager derivation consumed by non-tracked code or for interop. The eager model guarantees freshness at the cost of doing the work even if nothing reads the result that frame.
Trap
The naive answer is 'memo is for caching, computed is for derived values — same thing'. The scheduling is the difference: lazy (on-read) vs eager (on-change). Another trap: putting side effects inside a memo — a pure computation may run zero or many times, so network calls/DOM writes there are a bug. Candidates who can articulate lazy-vs-eager and purity constraints show real understanding.
Context
This probes the version/dirty propagation and equality checks — the mechanics that prevent redundant recomputation and update storms in large reactive graphs.
The mechanical answer
A memo tracks its source signals as dependencies. When a source changes, its version increments and it notifies observers including the memo; the memo flips its dirty flag but does not recompute. On a read, if dirty, it re-executes the computation in a tracking scope, stores the new value, and then compares the new value to the previous cached value using Solid's equality check. If the values are equal, it does not notify its own downstream observers — consumers that depend only on that memo see no update. This means a memo whose source changed to an equivalent value suppresses downstream work entirely. Additionally, if nothing reads the memo, it may never recompute (lazy), so 'source changed' alone never triggers execution.
Trap
The naive answer is 'a memo always recomputes when its source changes'. It recomputes on read, and only if the value actually differs. Another trap: assuming object references are compared deeply — Solid uses === by default, so returning a new object every time defeats equality suppression and propagates updates. Candidates who explain version bumping, lazy reads, and equality suppression show real engine-level understanding.