Summary
A signal is a reactive cell: read it and you subscribe; write it and only the computations that read it re-run. No component re-renders, no VDOM diff — only the precise DOM nodes or derived values that depend on changed state update. SolidJS, Preact, Angular, and Vue all ship this model today.
Jump to the interview angleSignal
A signal wraps a value and tracks every computation that reads it. When the value changes, those computations — and nothing else — are notified and re-evaluated.
Compare that to React: a setState call schedules the whole component to re-render, and React walks the VDOM tree to diff what changed. Fine-grained reactivity skips both steps — the dependency graph built at runtime is the schedule.
Three primitives appear in every implementation: a state signal (readable/writable), a computed (derived, memoized, lazy), and an effect (re-runs on dependency change). SolidJS uses createSignal, createMemo, and createEffect. Preact uses signal(), computed(), and effect(). Vue exposes the same model through ref(), computed(), and watchEffect().
How signals track dependencies
- 1
Push observer onto stack
When a computed or effect starts running, the runtime pushes it onto a global current-observer stack.
- 2
Signal records the subscriber
Every signal
getinside that function sees the stack and adds the running observer to its subscriber list. - 3
Write marks dependents dirty
When a signal value is set, it walks its subscriber list and marks each observer dirty. Effects schedule immediately; computeds wait until read.
- 4
Lazy vs. eager execution
Computed values are lazy — they re-run only when read after being dirtied. Effects are eager — they fire after the write. A computed no one reads never runs.
SolidJS — createSignal with derived createMemo
The component function runs exactly once. JSX compiles to fine-grained effects that write directly to DOM nodes — no VDOM, no re-render.
import { createSignal, createMemo, createEffect } from "solid-js";
function Counter() {
const [count, setCount] = createSignal(0);
const doubled = createMemo(() => count() * 2);
// Runs only when count() changes — not on every render.
createEffect(() => console.log("count:", count()));
return (
<button onClick={() => setCount(count() + 1)}>
{count()} × 2 = {doubled()}
</button>
);
}createSignal returns a getter/setter tuple. createMemo subscribes to count() and caches the result. The component function never re-runs.
Preact Signals — reactive state outside any component
Signals are plain objects. computed and effect auto-subscribe by reading .value during their first run — no component or hook required.
import { signal, computed, effect } from "@preact/signals";
const price = signal(100);
const qty = signal(3);
const total = computed(() => price.value * qty.value);
// Logs whenever price or qty changes.
effect(() => console.log("total:", total.value));
price.value = 120; // logs "total: 360"Signals are plain objects — no component needed. computed and effect auto-subscribe by reading .value during their first run.
Signals vs. component-based reactivity
Pros
- Only changed dependents update — no VDOM diff, no full component re-render.
- Derived state is memoized automatically; no manual `useMemo` needed.
- Effects subscribe precisely — no stale-closure or missing-dependency bugs.
- Scales to large reactive graphs without cascading re-renders.
- TC39 Stage 1 proposal may land `Signal.State`/`Signal.Computed` in JS itself.
Cons
- Reading a signal outside a tracked scope silently skips subscription.
- Circular dependencies between computeds cause infinite loops at runtime.
- Debugging reactive graphs is harder than stepping through a call stack.
- React's ecosystem — hooks, RSC, Suspense — doesn't map to signals without shims.
React's answer: React Compiler
React Compiler (1.0 stable, October 2025; works with React 17+) auto-inserts useMemo/useCallback at compile time to reduce re-renders — targeting the same problem without changing React's component model.
Interview angle
Interviewers want to know the contrast with React's model. Be concrete: signals track dependencies at the expression level and skip VDOM; React re-renders the component and diffs.
Soundbite: "Signals make the dependency graph explicit at runtime — only the expressions that read a changed value re-run, with no diff needed."
Key terms
- signal
- A reactive cell: reads subscribe the caller; writes notify all current subscribers.
- computed
- A derived, read-only signal that lazily re-evaluates when any dependency changes.
- effect
- A side-effectful callback that re-runs eagerly whenever its signal dependencies change.
- fine-grained reactivity
- Updates propagate to individual expressions or DOM nodes, not entire components.
- TC39 Signals proposal
- A Stage 1 proposal to add `Signal.State` and `Signal.Computed` as native JS primitives.