Skip to content
DevDepth
← Back to all articles

In-Depth Article

React Diff Algorithm: How Reconciliation Uses Type, Key, and lastPlacedIndex

Understand how React reconciliation stays near O(n): single-node matching, array diffing in ReactChildFiber, keyed reuse, and move detection with lastPlacedIndex.

Published: Updated: 9 min readreact-internals

When people say "React's diff algorithm," the source usually calls it reconciliation. That is the process where React compares the current Fiber children with the next React elements and decides what can be reused, what should be deleted, and what needs to be inserted or moved.

The most important correction to make early is this:

React is not trying to compute the perfect minimum edit script for the whole tree.

If React used a general-purpose tree diff, the cost would be far too high for normal UI updates. Instead, React uses heuristics that keep the common path close to O(n).

This article focuses on the child reconciler in ReactChildFiber.js, because that is where the famous behaviors around type checks, keys, array diffing, and lastPlacedIndex actually live.

If you want nearby context first, this article pairs well with how JSX becomes DOM in React, how React updates move from setState to a DOM update, and how time slicing and Fiber traversal work.

1. The heuristic model: fast enough beats globally optimal

The classic reconciliation docs explain React's speed with two main assumptions:

  • different element types usually produce different subtrees
  • developers can mark stable children with key

If you look at the actual child reconciler, a third practical rule becomes obvious:

  • React only compares children within the same parent boundary, so cross-level reuse is not something it tries to optimize

That is why React can keep the algorithm fast:

  • it does not search the whole tree for the globally best match
  • it does not try to preserve a node that moved to an entirely different parent
  • it focuses on sibling-level matching where UI updates usually happen

So the right mental model is not "React found the mathematically optimal edit sequence." It is:

React uses a set of local heuristics that are good enough for the vast majority of UI updates.

2. What the reconciler is actually comparing

During the render phase, the child reconciler compares two different shapes:

  • old children: the current Fiber chain, linked by child and sibling
  • new children: React elements, which may be a single element, an array, an iterator, text, or empty

That difference in shape is important.

The old side is a linked list. The new side is often an array of elements returned from JSX.

Because of that, React does not use one universal child-diff function for all cases. In ReactChildFiber.js, the work branches into specialized paths, especially:

  • reconcileSingleElement(...)
  • reconcileChildrenArray(...)

Those two paths explain most of what developers mean by "the React diff algorithm."

3. Single-node diff: reconcileSingleElement

When the new child is a single React element, React takes the single-element path.

The matching logic is simpler than the array case, but it still follows a clear order:

  1. Scan the old sibling chain for a matching key
  2. If the key matches, check whether the type is still compatible
  3. If both key and type are compatible, reuse the old Fiber through useFiber(...)
  4. Otherwise, delete the old branch and create a new Fiber

That order matters.

Key comes first

Identity is checked before type reuse. If the key does not match, React keeps scanning siblings because the matching child might be later in the old chain.

Type decides whether reuse is allowed

Once a matching key is found, React checks whether the element type still matches:

  • host type like div vs div
  • component type like UserCard vs UserCard
  • fragment/lazy compatibility in the special cases handled by the source

If the type is compatible, React clones the old Fiber into a new work-in-progress Fiber and updates the pending props.

If the type is different, React treats it as a remount.

Remaining siblings are deleted

A subtle but important point:

If the new child is only one element, then old siblings that are no longer needed get marked for deletion.

So single-element reconciliation is not just "reuse or recreate one node." It is also:

  • keep the one compatible child
  • delete everything else that used to sit beside it

4. Array diff: reconcileChildrenArray is the real core

The array path is where React's child diff becomes most interesting.

This is the case people usually mean when they talk about:

  • reordering lists
  • why key matters
  • why index keys can cause trouble
  • how React detects moves

The source follows a two-phase strategy.

5. Phase one: scan the common prefix with updateSlot

React starts optimistically.

It walks the old Fiber list and the new children array at the same time, from left to right, trying to reuse items in the same relative position.

This is the fast path:

  • compare the current old Fiber with the current new child
  • let updateSlot(...) decide whether they match
  • if they do, reuse and continue
  • if they do not, break out and switch to the slower map-based path

This is fast because many real updates are simple:

  • same order
  • same keys
  • maybe just different props

While that prefix match is working, React also calls placeChild(...) so it can keep track of whether reused nodes can stay where they are.

6. Fast-path exits: perfect match, pure insertions, or pure deletions

After the prefix scan, three easy cases can happen.

Case 1: new children are exhausted

If React has already consumed the whole new array, then any remaining old Fibers are deleted.

Case 2: old children are exhausted

If React has run out of old Fibers first, then every remaining new child is created as a new Fiber and marked for placement.

Case 3: both sides still have remaining items

This is the interesting case. It means the fast prefix assumption broke down somewhere in the middle.

That is when React switches to a Map-backed lookup.

7. Phase two: build a Map with mapRemainingChildren

Once the easy prefix is over, React creates a temporary Map from the remaining old children.

The keys for that Map are:

  • the explicit key if a child has one
  • otherwise the old index

This is what makes later lookups efficient.

Instead of scanning the rest of the old linked list again and again, React can look up a candidate old Fiber in approximately O(1) time while iterating through the remaining new array.

After that, React walks the remaining new children and calls updateFromMap(...).

For each new child:

  • if a matching old Fiber is found, React reuses it
  • if no match exists, React creates a new Fiber
  • any old children left in the Map after the pass are deleted

So the second phase is basically:

restore as much reuse as possible, then delete whatever the new list no longer needs.

8. How moves are detected: placeChild and lastPlacedIndex

This is the most practical part of the algorithm.

Once React has found a child to reuse, it still has to answer:

Can this child stay where it is, or does it need to move?

That decision happens inside placeChild(...).

The key variable is lastPlacedIndex. It records the largest old index React has seen so far among children that can stay in order.

The move rule is:

  • if the reused child's old index is less than lastPlacedIndex, mark it with Placement
  • otherwise it can stay in place, and lastPlacedIndex moves forward to that old index

The simplified source logic looks like this:

if (current !== null) {
  const oldIndex = current.index;
  if (oldIndex < lastPlacedIndex) {
    newFiber.flags |= Placement;
    return lastPlacedIndex;
  } else {
    return oldIndex;
  }
} else {
  newFiber.flags |= Placement;
  return lastPlacedIndex;
}

That means Placement covers two different situations:

  • a brand new insertion
  • an existing child that must move

9. A concrete example: A B C D becomes A C B D

Suppose the old order is:

A -> B -> C -> D

And the new order is:

A -> C -> B -> D

Here is what happens conceptually:

  1. A matches early and stays in place. lastPlacedIndex becomes 0.
  2. C is found later in the old list with old index 2. Since 2 >= 0, C can stay in place from React's perspective. lastPlacedIndex becomes 2.
  3. B is found with old index 1. Since 1 < 2, React marks B with Placement.
  4. D is found with old index 3. Since 3 >= 2, D stays in place.

So React does not "move everything after the mismatch." It only marks the nodes whose relative order is now behind a node that already established a later stable position.

That is why lastPlacedIndex is such a good mental hook:

It turns list movement into a simple monotonic order check.

10. Why keys matter so much

Keys are not just there to silence a warning. They are how React preserves identity among siblings.

When keys are stable, React can:

  • find the right old Fiber in the Map phase
  • preserve component state more reliably
  • distinguish "this item moved" from "this item is brand new"

When keys are unstable, React loses that advantage.

That is why using array index as a key can be dangerous for dynamic lists.

If you insert, remove, or reorder near the beginning of a list, index-based keys make many siblings appear to have changed identity. That can cause:

  • unnecessary remounts
  • reused state on the wrong visual item
  • more placement work than needed

Index keys are not always catastrophic. For a truly static list that never reorders, they can be acceptable. But for real interactive collections, stable data IDs are far better.

11. What React intentionally does not optimize

The current child reconciler makes some tradeoffs on purpose.

It does not try to reuse across parent boundaries

If a node moves to a different parent or different structural level, React does not try to detect that as a preserved subtree move. It will remount in the new location.

It does not chase the global minimum edit script

That would be too expensive for normal UI rendering.

It is forward-only

There is even a comment in the source that the array algorithm does not optimize by searching from both ends, partly because Fibers do not have backpointers for that style of algorithm.

That is a very React-looking tradeoff:

  • keep the structure simple
  • optimize the common path
  • accept that some uncommon reorder patterns are not globally optimal

12. Best practices that follow directly from the algorithm

Once you understand the reconciler, several React best practices stop feeling arbitrary.

Use stable keys from data

If your data already has an ID, use it as the key.

Keep subtree structure reasonably stable

If a piece of UI changes parent boundaries often, React will not preserve it the same way it can preserve sibling-level updates.

Treat type changes as identity changes

If you switch from one element or component type to another, React will not try to preserve the old subtree just because it sits in the same position.

Remember that reconciliation is sibling-local

React's best reuse story happens among siblings under the same parent, especially when the keys are stable.

13. The summary worth remembering

If you only want one compact mental model, keep this:

  • single child updates go through reconcileSingleElement
  • list updates go through reconcileChildrenArray
  • React first tries the fast prefix path
  • then it falls back to a Map for the remaining old children
  • lastPlacedIndex decides whether a reused child stays or moves
  • Placement means either insertion or movement

That is why React reconciliation is fast in practice. It is not a perfect tree-edit algorithm. It is a carefully chosen set of heuristics, implemented in ReactChildFiber.js, that optimize for the update patterns UI code usually produces.

Reviewed by

DevDepth Editor

Editor and frontend engineering writer

DevDepth publishes practical guides on React, Next.js, TypeScript, frontend architecture, browser APIs, and performance optimization.

Each article should be reviewed for technical accuracy, code clarity, metadata quality, and internal-link fit before it goes live.

Last editorial review: 2026-03-17

Contact the editor