Comparisons
Pairs that get conflated in real conversations, and in real pull requests. Neither column wins — what decides is the requirement. Each record leads with the confusion, because the confusion is the reason the record exists.
Virtual DOM vs Fine-grained reactivity
"The virtual DOM is fast" is the claim, and it is not a claim at all until you name what it is faster than and at what. It is faster than naively rebuilding the DOM by hand on every state change, because DOM mutation and the layout it triggers are the expensive part and diffing avoids most of it. It is not faster than not diffing — a fine-grained system that knows which text node changed does strictly less work than one that re-runs a component and compares two trees to find out. What the virtual DOM buys is a programming model: render as a pure function, no subscription bookkeeping, and no rules about how you may read a value. The costs are real on both sides. The diffing model spends main-thread time proportional to the re-rendered subtree, which is why memoization and keys exist and why an unmemoized provider can re-render half an app. The fine-grained model has almost no diff cost but the reactivity is *where you read the value*, so destructuring a value out of its tracking context silently breaks updates, and dependency graphs can be harder to debug. Neither is the winner; they fail differently, and the failures land on different people.
A model where rendering is a pure function of state and the framework decides what changed. Predictable, easy to reason about as a mental model, and the cost is a diff proportional to the tree you re-rendered.
A model where reading a value subscribes to it, so a change notifies precisely the computations and DOM bindings that depend on it, with no component re-execution and no diff.
| Dimension | Virtual DOM — re-run the component, diff a description of the output, patch the difference | Fine-grained reactivity — a dependency graph updates exactly the bindings that changed |
|---|---|---|
| Unit of update | A component re-render, then a diff | A single binding or computation |
| Work on a state change | Proportional to the re-rendered subtree | Proportional to the number of dependents |
| Mental model | UI is a pure function of state | Values are observable and reads create subscriptions |
| Tuning tools | Keys, memoization, splitting components | Rarely needed; correctness of tracking matters more |
| Typical bug | Re-rendering far more than necessary | A read that escaped tracking, so nothing updates |
| Runtime cost | A reconciler that ships with the app | A smaller runtime, often with more compile-time work |
| DOM writes | Only the diff is applied — this part is the same | Only the changed binding is applied |
| Fair comparison needs | A workload, a device and a device class | The same three — a benchmark without them says nothing |