# Signals & Fine-Grained Reactivity > Observable values that re-run only their exact dependents. Frontend architecture guidance from fearchitect, written by Abas Turabli and last reviewed 2026-06-21. Source: https://fearchitect.com/topics/signals-fine-grained-reactivity Use it as reference for the task at hand. Before changing code, check this guidance against the codebase: where the code already makes a different, deliberate choice, flag the conflict instead of rewriting it. Library APIs move faster than this guide, so confirm exact signatures in the official docs linked at the end. ## 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. ## Signal 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()`. ## Diagram ```mermaid graph LR S1["signal: price"] --> C1["computed: total"] S2["signal: qty"] --> C1 C1 --> E1["effect: log total"] C1 --> E2["DOM: <span>total</span>"] ``` A write to price or qty propagates only to computed:total and its two dependents — nothing else re-runs. ## 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 `get` inside 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. **SolidJS counter with derived memo** ```tsx 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 ( ); } ``` `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. **Preact Signals — signal and computed** ```ts 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. ## Note: 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. ## 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. ## Related topics - [Client State Management](https://fearchitect.com/topics/client-state-management.md): Decide where UI state lives; pick the right tool for the scope. - [Render Performance](https://fearchitect.com/topics/render-performance-patterns.md): Skip renders, defer slow work, and virtualize long lists. - [State Machines](https://fearchitect.com/topics/state-machines.md): Model UI as explicit states with typed transitions to kill impossible states. - [Optimistic UI & Mutations](https://fearchitect.com/topics/optimistic-ui-mutations.md): Update the UI before the server replies; roll back on error. - [Real-time: WebSockets vs SSE vs Polling](https://fearchitect.com/topics/realtime-websockets-sse-polling.md): Match the right real-time transport to your data-flow direction. - [REST vs GraphQL vs tRPC](https://fearchitect.com/topics/rest-vs-graphql-vs-trpc.md): Three API styles with distinct fetch, type, and caching trade-offs. ## Further reading - [TC39 Signals proposal — GitHub](https://github.com/tc39/proposal-signals) - [Preact Signals guide](https://preactjs.com/guide/v10/signals/) - [SolidJS — Fine-Grained Reactivity docs](https://docs.solidjs.com/advanced-concepts/fine-grained-reactivity) - [Angular — Signals overview](https://angular.dev/guide/signals) - [React Compiler — 1.0 stable release notes](https://react.dev/blog/2025/10/07/react-compiler-1)