How Does React Diffing Algorithm Work?

Intermediate12 min interview
Skills tested:
Understanding the two-heuristic model: element type comparison and key-based list matchingKnowing what happens when element types differ at the same position (full subtree replacement)Knowing what happens when element types match (props update, children recursed)Explaining why keys are required for lists and what happens when they are missing or incorrectUnderstanding that switching component types unmounts the component and resets its state

Advertisement

🧩 Scenario

Diffing knowledge directly affects the correctness and performance of React apps. The wrong key choice causes incorrect list updates and lost form state. Changing a component type inadvertently (for example, conditionally rendering ComponentA or ComponentB at the same position) destroys and remounts the subtree on every toggle, losing all local state.

Architecture Walkthrough

Heuristic 1: Element Type Comparison

When React reconciles two trees, it compares elements at the same position, level by level. The first question is: do the two elements have the same type? If the types differ (e.g., a <div> became a <section>, or <UserCard> became <ProductCard>), React tears down the old subtree completely. All DOM nodes in the subtree are removed. All React component instances are unmounted, losing their state. The new subtree is mounted from scratch.

If the types match, React preserves the existing DOM node or component instance and updates only what changed. For DOM elements, it computes the props diff and applies only the changed attributes. For component elements, it re-renders the component with the new props, potentially triggering its own reconciliation.

Heuristic 2: Key-Based List Matching

When a component renders a list of children, React needs a way to match children between renders that is stable across order changes. Without keys, React uses position: the first child is matched to the first child, the second to the second, and so on. If an item is prepended to a list, every existing item is at a new position and React treats every one as changed, even if the items themselves are identical.

Keys solve this by giving each child a stable identity. React builds a map from key to element for the new list, then matches by key rather than position. Only items whose content changed are updated. Items with new keys are mounted. Items whose keys no longer exist are unmounted.

Component State and Type Changes

State is tied to the component instance, which is tied to the position in the tree. When a component type changes at a given position, React unmounts the old instance (discarding its state) and mounts the new type. This is why a toggle between two different component types always resets state: <Input type="text"> and <Input type="email"> have the same type (Input) so state is preserved, but <TextInput> and <EmailInput> are different types so state is destroyed on each toggle.


Key Code Explained

// Type match: React updates props, preserves DOM node and component state
// Before: <button className="primary">Save</button>
// After:  <button className="secondary">Save</button>
// Result: React patches className attribute. The button DOM node is reused.

// Type change: React tears down and remounts the entire subtree
function Toggle({ showA }: { showA: boolean }) {
  return showA ? <ComponentA /> : <ComponentB />;
}
// Every time showA changes, ComponentA unmounts (losing state) and ComponentB mounts.
// ComponentA and ComponentB are different types, even if their JSX looks identical.

// Wrong key: index causes incorrect list diffs for dynamic lists
function BadList({ users }: { users: User[] }) {
  return (
    <ul>
      {users.map((user, index) => (
        // If user at index 0 is deleted, user at index 1 becomes index 0.
        // React thinks the first item changed (from user[0] to user[1]),
        // rather than realizing user[0] was removed entirely.
        <UserCard key={index} user={user} />
      ))}
    </ul>
  );
}

// Correct key: stable, unique identifier
function GoodList({ users }: { users: User[] }) {
  return (
    <ul>
      {users.map((user) => (
        // user.id is stable. If user[0] is deleted, React matches remaining
        // items by ID and unmounts only the deleted user's card.
        <UserCard key={user.id} user={user} />
      ))}
    </ul>
  );
}

// Using key to force remount when needed
// If you want a component to fully reset its state when a prop changes,
// change its key. React sees a new key as a new element and remounts.
function UserDetail({ userId }: { userId: string }) {
  return <UserForm key={userId} userId={userId} />;
  // When userId changes, UserForm is unmounted and remounted fresh.
  // Its internal state (draft values, focus, etc.) is reset.
}

The key prop as a reset mechanism (the last example) is an important pattern. Without it, if UserForm had internal draft state and userId changed, the form would retain the previous user's draft values. Adding key={userId} forces React to treat the new userId as a completely different element, remounting with a clean state.


Tradeoffs

Key strategyCorrect for insertion/deletionCorrect for reorderState preservationUse when
No key (position)NoNoBy positionStatic lists that never change
Index as keyNoNoBy positionEffectively never
Stable ID as keyYesYesBy IDAny dynamic list

What Interviewers Actually Check

  • Whether you know the two heuristics: same-type update vs different-type teardown, and key-based list matching
  • Whether you can explain why array index is a bad key for dynamic lists
  • Whether you know that a component type change destroys state
  • Whether you know the "key as reset" pattern
  • Whether you can explain why the algorithm is O(n) rather than O(n^3)

Follow-Up Questions

  1. React 18 introduced concurrent rendering. How does it change when and how reconciliation runs compared to React 17?
  2. What is the fiber architecture and how does it enable time-sliced rendering?
  3. If you have a list of 10,000 items and update one item's data, does React re-render all 10,000 items?
  4. How does React.memo interact with the diffing algorithm?
  5. What happens to the DOM when React replaces a subtree due to a type mismatch vs when it updates a subtree due to a props change?

Common Candidate Mistakes

  • Using array index as key and not knowing that deletions or insertions from the middle produce incorrect diffs
  • Using Math.random() as a key, which regenerates on every render and forces every list item to remount on every render
  • Thinking React compares the real DOM rather than the virtual DOM trees
  • Not knowing that changing the component type at a position destroys the component's state
  • Thinking keys must be globally unique across the entire application (they only need to be unique among siblings)

Interview Readiness Checklist

Before you leave this question, make sure you can answer:

  • Can you explain the two main heuristics React uses: type comparison and key matching?
  • Can you describe what happens when element types differ at the same tree position?
  • Can you explain why array index is a poor key for dynamic lists where items are added, removed, or reordered?
  • Can you describe what happens to component state when its type changes at the same position?
  • Can you explain the "key as reset" pattern and when to use it?

Summary

React's diffing algorithm reduces tree comparison from O(n^3) to O(n) by applying two heuristics. First, elements at the same position in the tree are compared by type. If types differ, the old subtree is torn down completely and the new one is mounted from scratch, discarding all component state in the old subtree. If types match, React preserves the existing node and applies only the changed props.

Second, for lists of children, React uses keys to match elements by stable identity rather than by position. A key tells React which item in the old list corresponds to which item in the new list. Without stable keys, React falls back to position matching, which produces incorrect diffs when items are added, removed, or reordered. Array index is the most common bad key choice because it shifts for all subsequent items when an item is inserted or deleted from the middle.

Understanding the diffing algorithm explains several common bugs: form state lost when switching between two different component types, list items showing the wrong content after a deletion, and stale state persisting when a prop changes but the component type does not. The key-as-reset pattern (assigning a new key to force a full remount) is the direct application of this knowledge.

Frequently Asked Questions

What is the time complexity of React diffing?

O(n) where n is the number of nodes in the tree. Full tree comparison is O(n^3), but React uses heuristics (element type and key comparison) that reduce it to O(n) in practice.

Advertisement


Stay Updated

Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.

Advertisement