DesignDev.io logoDesignDev.io logo

  1. Home
  2. /React Foundations
  3. /React Reconciliation Explained: What the Diffing Algorithm Actually Does
← Back to home

React Reconciliation Explained: What the Diffing Algorithm Actually Does

Alex Chen

Alex Chen

Cover Image for React Reconciliation Explained: What the Diffing Algorithm Actually Does
Alex Chen

Alex Chen

January 17, 2026·6 min read
Updated April 26, 2026
Series:React Foundations
Level:Intermediate
Tags:reactweb
Assumes:Comfortable with React components, hooks, and the key prop

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:

  1. Elements of different types produce different trees — if the root element type changes, tear everything down and build fresh
  2. The developer can hint at stable identity across renders — the key prop

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:

  1. Walk both trees simultaneously, position by position
  2. At each position: same type → update in place. Different type → destroy and rebuild
  3. For lists: match children by key if provided, by position if not
  4. 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


More Stuff

Building a Badge Component — Variants, Sizes, and Dot Indicators
April 3, 2026·3 min read

Building a Badge Component — Variants, Sizes, and Dot Indicators

Badge is the first purely presentational primitive in this series — no form integration, no event handling, no focus management. That simplicity is deceptive. A Badge that looks right in isolation often breaks inside a nav item, a table cell, or an avatar. The decisions that make it composable are worth getting right now.

Muhammad Athar

Muhammad Athar

Building a Label Component That Actually Connects to Its Field
April 3, 2026·3 min read

Building a Label Component That Actually Connects to Its Field

We've referenced a Label component in every form field article so far without building it. That's intentional — you need to understand what the Label connects to before you understand what it needs to do. Now that we have Input, Textarea, Checkbox, RadioGroup, Select, and Toggle, the Label's job is clear: it's not just styled text. It's the accessible name of a form field, and it carries required indicators, optional markers, and disabled state as part of that responsibility.

Muhammad Athar

Muhammad Athar

DesignDev.io logoDesignDev.io logo

Explore

  • Home
  • Search
  • Authors
  • Series
  • About Us

Account

  • Sign in

Legal

  • Privacy Policy
  • Terms of Service

© 2026 DesignDev.io · All rights reserved

Built with Next.js · Tailwind · Sanity · Vercel