React's Reconciliation Algorithm Explained

Image by Brice Cooper.

One of React's biggest strengths is its ability to update the UI efficiently. Instead of rerendering the entire page every time something changes, React compares the new state with the previous one and only updates what is necessary. This process, known as reconciliation, ensures smooth, fast updates without unnecessary work.

But how does React decide which parts of the UI need updating? That's where the reconciliation algorithm comes in. In this article, I explore how it works, why the virtual DOM is important, and what we can do to optimise rendering performance.


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.

By using the virtual DOM, React can determine precisely what has changed before touching the real DOM, keeping updates as fast as possible.


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 then compares the new virtual DOM with the previous one. Instead of rerendering everything, it finds the differences (this process is called "diffing").

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 track items between renders. Without unique keys, React might remove and recreate items rather than reordering them efficiently.

Inefficient Rendering (No Keys):

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

React may treat each <li> as a new element, leading to unnecessary rerenders.

Optimised Rendering (Using Keys):

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

Adding a unique key allows React to detect which items have changed, been added, or removed, making the update process much more efficient.


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 while 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 while 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

React's reconciliation algorithm ensures efficient UI updates by comparing the virtual DOM with the previous state and applying only the necessary changes. By understanding how this process works, we can write betterperforming applications and avoid unnecessary rerenders.

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.

By following these best practices, we can keep our React applications running smoothly and avoid performance bottlenecks as our projects grow.


Need a senior engineer involved?

I can work directly in the codebase, review the architecture, or support the team through delivery when the work needs more than extra hands.