React's Reconciliation Algorithm Explained

Hero image for React's Reconciliation Algorithm Explained. Image by Brice Cooper.
Hero image for '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 is already optimised, but we can take it a step further by following best practices.

1. Prevent Unnecessary ReRenders with React.memo

If a component renders the same output for the same props, we can prevent unnecessary updates using React.memo:

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

Now, ExpensiveComponent only rerenders if value changes.

2. Use useCallback and useMemo to Avoid Recomputations

useCallback prevents functions from being recreated unnecessarily whilst useMemo caches expensive calculations.

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. Avoid Inline Functions and Objects in JSX

Creating new functions inside JSX can cause unnecessary rerenders.

Less Efficient:

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

More Efficient:

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 help reduce unnecessary rerenders.
  • Minimising DOM updates

    keeps applications fast and responsive.

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


Categories:

  1. Front‑End Development
  2. Guides
  3. JavaScript
  4. React