React's Reconciliation Algorithm Explained

Abstract image used to represent React's Reconciliation Algorithm Explained
Image by Brice Cooper.

When state or props change, React renders the affected component tree, reconciles that result with the previous one, and commits the necessary DOM changes. Reconciliation is the comparison step, not a promise that every render is cheap or that the browser avoids all work.


What is the Virtual DOM?

Before looking at reconciliation, we need to understand the virtual DOM (VDOM) and why React uses it.

When we update state in React, it does not modify the real DOM immediately. Instead, React updates a virtual representation of the DOM. This allows React to:

  1. Batch updates efficiently

    – Rather than making multiple small changes, React groups them together to improve performance.
  2. Minimise DOM manipulation

    – The real DOM is slow to update, so React only modifies what is necessary.

React compares the new description of the interface with the previous result before committing DOM changes. Its heuristics make common updates practical, but they do not guarantee the fastest possible result for every component tree.


How React's Reconciliation Algorithm Works

When a component's state or props change, React follows these steps to update the UI efficiently:

1. Generate a New Virtual DOM

React first creates a new virtual DOM tree that reflects the latest state.

2. Compare the New and Old Virtual DOM (Diffing)

React compares the new result with the previous one to decide which DOM changes to commit. Components may already have rendered during that process; rendering a component is different from replacing its DOM nodes.

For example, consider this simple component:

const ExampleComponent = ({ title }: { title: string }) => {
  return <h1>{title}</h1>;
};

If title changes from "Hello" to "Welcome", React does not replace the entire <h1> element. It only updates the text inside it, keeping the process efficient.

3. Update the Real DOM

After identifying what has changed, React updates only the affected elements in the real DOM. This keeps updates fast and smooth.


How React Determines What to Update

React follows two key rules to optimise how elements are updated:

1. Elements of Different Types Trigger a Full Re‑Render

If an element's type changes, React does not try to update it, it destroys the old element and creates a new one.

const Component = ({ isHeading }: { isHeading: boolean }) =>
  isHeading ? <h1>Hello</h1> : <p>Hello</p>;

Since <p> and <h1> are different element types, React removes the <p> and replaces it with <h1>, instead of just changing the text.

2. Keys Optimise List Updates

When rendering lists, React uses keys to match sibling items between renders. Without explicit keys, it matches by position. That can reuse a component or DOM node for a different item after insertion or reordering, with surprising consequences for local state or uncontrolled inputs.

Inefficient Rendering (No Keys):

const List = ({ items }: { items: string[] }) => (
  <ul>
    {items.map((item) => (
      <li>{item}</li>  // No key provided
    ))}
  </ul>
);

Here, each <li> is matched by its position. Inserting an item at the start can cause existing nodes to be reused for different data; it does not necessarily mean every node is destroyed and recreated.

Optimised Rendering (Using Keys):

const List = ({ items }: { items: string[] }) => (
  <ul>
    {items.map((item) => (
      <li key={item}>{item}</li>  // Keys help React track items
    ))}
  </ul>
);

A stable key lets React match an item even when its position changes. In this small example the strings must be unique among siblings; for real records, a stable identifier is usually a better choice. Keys preserve identity, rather than preventing every component render.


Improving Performance with Reconciliation

React's reconciliation algorithm already applies its update heuristics. Further optimisation should begin with measurement and a specific rendering cost.

1. Prevent Unnecessary ReRenders with React.memo

If a component is costly to render and receives props whose identities remain equal, React.memo may allow React to reuse its last result during a parent render. It is a performance optimisation, not a correctness tool or a guarantee.

const ExpensiveComponent = React.memo(({ value }: { value: string }) => {
  console.log("Rendered");
  return <p>{value}</p>;
});

Here, ExpensiveComponent can skip a parentdriven render whilst value is unchanged. Its own state or a context it reads can still cause it to render, and React may render it for other reasons.

2. Use useCallback and useMemo Where Identity or Calculation Cost Matters

useCallback keeps a function reference stable between renders whilst its dependencies are unchanged. useMemo reuses a calculated value under the same condition. Both add work and should be used when a measured calculation or identitysensitive consumer benefits.

const handleClick = useCallback(() => {
  console.log("Clicked");
}, []);

const expensiveValue = useMemo(() => computeExpensiveResult(), []);

3. Use Stable Keys in Lists

Always use unique, stable keys when rendering lists to help React track changes efficiently.

4. Understand Function and Object Identity

Creating a function or object during render gives it a new identity, but that alone does not make the current component render again. Identity matters when the value is passed to a memoised child, used as an Effect dependency, or consumed by another identitysensitive API.

Inline Handler:

<button onClick={() => console.log("Clicked")}>Click me</button>

Named Handler:

const handleClick = () => console.log("Clicked");
<button onClick={handleClick}>Click me</button>;

Wrapping Up

Key Takeaways

  • React compares the virtual DOM with the previous state to update only the necessary parts of the UI.
  • Elements of different types

    are replaced rather than updated.
  • Keys in lists

    help React track and reorder items efficiently.
  • Optimisations like

    React.memo, useCallback, and useMemo can reduce measured work when their dependency and propidentity conditions are satisfied.
  • Minimising DOM updates

    can help when profiling shows that browser work is a bottleneck.

Give list items stable keys, avoid Effects that create render loops, and use memoisation only where measurement shows that repeated work matters. A new function or object is not automatically a performance bug. Reconciliation becomes easier to reason about when you separate rendering work from the DOM changes React commits after reconciliation.


Want to find out more?

If you need senior handson support with a complex React or Next.js platform, migration, performance issue, or technical SEO problem, send me the context and I'll tell you where I can help.