React Reconciliation Explained: What the Diffing Algorithm Actually Does

Most React developers have a working mental model of the virtual DOM. Most of those mental models are imprecise enough to cause real bugs. Here's what React's diffing algorithm actually does — and the two heuristics that explain why key, memo, and component type all matter.
The virtual DOM explanation you've heard goes something like this: React keeps a copy of the DOM in memory, compares the new copy against the old one when state changes, and only updates what changed. Efficient. Smart.
That explanation is true but incomplete. It leaves out the specific decisions React makes during the comparison — decisions that directly determine when your components re-render, when they reset their state, and when an animation you expected to replay doesn't.
Understanding the diffing algorithm isn't academic. It's the reason the key prop from Article #3 works. It's the mental model behind React.memo. It explains why changing a component's type resets all its state. These aren't arbitrary React behaviours — they follow directly from how the differ works.
The naive diffing problem
Comparing two trees of arbitrary structure is an O(n³) problem — for a tree of 1,000 elements, that's a billion operations. Unacceptable for UI rendering.
React's differ reduces this to O(n) by making two assumptions:
- Elements of different types produce different trees — if the root element type changes, tear everything down and build fresh
- The developer can hint at stable identity across renders — the
keyprop
These aren't perfect assumptions. There are cases where they produce suboptimal results. But they make the algorithm fast enough to run on every render — which is what makes React practical.
Heuristic 1: element type determines identity
When React compares two trees, it starts at the root element and works downward. The first question it asks at each position: are the element types the same?
Same type → update in place. Different type → destroy and recreate.
Here's what that means concretely:
React sees div in both positions. Same type. It updates the className attribute on the existing DOM node and continues diffing the children. UserProfile is also the same type — React updates its props. No state is lost. No DOM node is destroyed.
Now with a type change:
React sees div in the old tree and section in the new tree. Different types. It destroys the entire div subtree — including the UserProfile component inside it and all its state — and builds a fresh section subtree from scratch.
This is why changing a wrapper element's type resets child state. The child didn't change, but its parent's type did, so React treats the entire subtree as new.
Type changes with component types
The same rule applies to component types:
Even if AdminDashboard and UserDashboard render identical output, React sees different component types at the same tree position and destroys the entire AdminDashboard instance — its state, its effects, its child components — and mounts a fresh UserDashboard.
This is the mechanism behind one of the most common React bugs: a conditional that switches between two different component types, both of which render a form with the same fields. The user fills in the form, the condition changes, and the form resets — because React unmounted one component type and mounted another.
If the two forms are genuinely different enough to be separate components, the state reset is expected and correct. If the fields are the same and the reset is surprising — consolidate into one component with a prop.
Heuristic 2: keys signal stable identity in lists
When React diffs a list of children, it needs to match each child in the new list to a child in the old list. Without key, it matches by position — the first old child matches the first new child, second to second, and so on.
Matching by position works fine when items are only added at the end:
React matches A→A, B→B, C→C and creates a new D. Correct.
It breaks down when items are added at the beginning or reordered:
Without keys, React matches A→B, B→C, C→D and creates a new item at the end — four updates instead of one insert. With string or number keys, React can match items by identity across positions:
React sees that B, C, and D exist in both trees and moves them. Only A needs to be created. One operation instead of four.
The index key bug — animated
This is the bug that bites most developers eventually. Using the array index as a key:
Walk through what happens when an item is removed from the middle of the list:
"Cherry" inherited "Banana's" input state because they share the key 1. The component instance wasn't recreated — React thought it was the same item that just got new props.
With a stable, data-derived key:
Each item's state belongs to its key. Removing "Banana" removes only its instance and its state.
Fibre: what React 18 adds on top
The original React differ — the one described above — was synchronous. It computed the full diff and committed all changes in one uninterruptible pass. For small trees, imperceptible. For large trees, it could block the main thread long enough to cause dropped frames.
React Fibre, introduced in React 16 and extended in React 18, adds interruptibility to the differ. Instead of one synchronous pass, React breaks the work into units — "fibres" — that can be paused, abandoned, and resumed.
This is the infrastructure that makes useTransition and useDeferredValue possible. A state update wrapped in startTransition is scheduled as interruptible work — React can pause diffing the transition update to process a more urgent update (like a keystroke) and resume afterward.
The diffing rules haven't changed. Type identity and key-based matching work the same way. What Fibre adds is the ability to prioritise which parts of the diff run first.
Practical consequences
Changing a component's type at the same position resets all state. If you have a bug where state is lost unexpectedly, check whether a parent component is conditionally rendering different component types at the same position.
Keys must be stable and unique within a list. Math.random() as a key creates a new key every render — React unmounts and remounts every item on every render. Index keys cause state bugs when items are reordered or removed. Use a stable ID from your data.
Component identity is determined by tree position and type, not component name. React doesn't care that you named your component UserProfile. It cares that a component of type UserProfile appeared at position X in the tree. Same position, same type, same instance. Different type or different position, new instance.
The key trick from Article #3 works because of this. Changing the key on a component forces React to see a new identity at that position — different key, new instance, fresh state — even though the component type is unchanged.
The mental model to keep
When React compares old and new trees:
- Walk both trees simultaneously, position by position
- At each position: same type → update in place. Different type → destroy and rebuild
- For lists: match children by
keyif provided, by position if not - Matched children → update props. Unmatched old children → unmount. New unmatched children → mount
Everything else — memo, key tricks, Fibre priorities — builds on top of these four rules.
Once this model is clear, the behaviours that felt arbitrary start feeling inevitable. Of course changing a parent's type resets children — React destroyed the parent. Of course index keys cause state bugs with reordering — the match is by position, not identity. Of course the key trick resets state — a new key means a new identity means a new instance.
Up next: Building a fully accessible modal from scratch in React — focus management, ARIA attributes, and the scroll lock that doesn't break iOS Safari.
Related: The key prop is not just for lists — here's what you're missing — the practical use of React's identity system covered in this article.
Alex Chen is a senior frontend engineer who writes about React patterns, JavaScript internals, and the decisions that separate maintainable codebases from ones that fight back. Opinionated by design.
Photo by Artur Opala on Unsplash

