Skip to main content

Command Palette

Search for a command to run...

How React Virtual DOM works under the Hood

Updated
7 min read
How React Virtual DOM works under the Hood
A
my work defines me

React is the modern way to build frontend and handleing the problem of reloading again and again on mounting a node in a page and minimise the compute. The topics that we are going to cover in this blog are mentioned below.

  • What problem the Virtual DOM solves

  • Difference between Real DOM vs Virtual DOM

  • Initial render process in React

  • How state or props change triggers re-render

  • Creation of a new Virtual DOM tree

  • What diffing (reconciliation) means

  • How React finds minimal required changes

  • Updating only changed nodes in the Real DOM

  • Why this approach improves performance

  • High-level overview of React render → diff → commit flow


Problem that DOM was facing earlier

As we know the JS DOM manipulation of net picking any tag by using 'window' element and then find element by ID property. See example below

window.document.getElementById('h1')  // this will taget the h1 tag

But the real problem arrises when you try to update something on the existing page for example!

Consider a notification that didn't update because the browser was preoccupied with other tasks, failing to allocate resources to refresh the notification we had already seen. As a result, the notification continued to appear on your web page, leading to what was known as Ghost Notification. Understand with the diagram below!

This problem of page reloading again and again was first tackled by stackoverflow and various tech like JQuerry comes to solve them. But the scale of this problem was first acheived by facebook where they have 100M+ users. So, facebook tech team come up with the solution of Virtual DOM.


Virtual DOM

Virtual DOM is nothing but a similar like DOM. But the control of the page is out from the browser's hand now we will control the page.

See the difference between DOM and Virtual DOM

So, now we know what React is for! and also the technical terms like Fibre, Reconsiliation are just the fancy names we know what they actually mean.


Initial Render Process in React

React’s rendering has evolved from heavy, class-based, synchronous initialization to a streamlined, hook-based, and concurrent architecture. Older React used large root mounting via ReactDOM.render() and bulky lifecycle methods. Today, React leverages asynchronous roots (createRoot), lazy-loading, and server-side rendering for optimal performance.

The Early Days: Class Components & Synchronous Mounting

In the early days of React, rendering was rigid, synchronous, and heavily reliant on ES6 class components

  • The Entry Point: You initialized an application using the global ReactDOM.render() method, targeting a single root DOM element.

  • Component Birth (Mounting Phase): The initial render triggered a strict sequence of lifecycle hooks. React would call the constructor(), execute componentWillMount(), render the component, and finally trigger componentDidMount() once the DOM was updated.

  • Performance Bottlenecks: Every UI update in the early days caused React to re-evaluate the entire component tree, leading to layout thrashing and slow load times on heavy, data-rich pages.

The Current Status: Functional Components & Concurrent React

  • The Modern Entry Point: Initialization uses createRoot() from react-dom/client. This asynchronous root enables Concurrent Features, allowing React to pause, resume, or even abandon rendering work to prioritize user interactions.

  • Hooks over Lifecycles: The heavy mounting lifecycle methods have been entirely replaced by hooks like useState and useEffect. Instead of componentDidMount, code runs inside useEffect during the commit phase.

  • Server-Side Rendering (SSR) & Streaming: Rendering is no longer strictly client-side. Frameworks like Next.js and Remix use React's built-in streaming SSR to send UI HTML to the client as soon as it's ready, drastically improving First Contentful Paint (FCP).


How state or props change triggers re-render?

State and props changes trigger re-renders through a structured process called the Render and Commit Cycle. While they both lead to UI updates, they function as different types of triggers within the React engine.

The Trigger: State vs. Props

  • State Change (The Active Trigger) A re-render always starts with a state change. When you call a state setter (e.g., setCount), React schedules an update for that specific component.

  • Props Change (The Reactive Trigger) A component does not technically "trigger" its own re-render when props change; rather, it re-renders because its parent component re-rendered. By default, if a parent component renders, React recursively re-renders all of its children to ensure the UI stays in sync.

The Render Phase (Virtual DOM & Diffing)

Once an update is scheduled, React enters the Render Phase to calculate what needs to change.

  • Virtual DOM Generation: React calls the component function again to generate a new tree of React Element (Virtual DOM).

  • Reconciliation & Diffing: React compares the new Virtual DOM with the previous snapshot using a diffing algorithm.

  • Fiber Architecture: Under the hood,React Fibre manages this work as independent units, allowing it to pause or prioritize certain updates for better responsiveness.

The Commit Phase (Updating the Real DOM)

After identifying the differences, React moves to the Commit Phase:

  • DOM Updates: React applies only the necessary changes (additions, deletions, or attribute updates) to the actual Browser DOM in a single synchronous batch.

  • Post-Render Effects: Once the DOM is stable, React runs lifecycle methods like componentDidUpdate or hooks like useEffect.


How react finds the minimal required changes?

React finds the minimal required changes to the UI through a process called Reconciliation, which is powered by a Diffing Algorithm.

Because comparing two full trees of elements can be computationally expensive (taking O(n^3) time), React uses a "heuristic" algorithm that reduces this complexity to O(n) (linear time) by making two key assumptions:

  • Element Type Comparison

  • The Power of "Keys"


Why this approach improves the performance?

React’s approach improves performance by focusing on efficiency in the browser rather than just raw execution speed in JavaScript.

  • Minimizing Expensive DOM Operations

  • Batching Updates: React doesn't immediately update the screen every time you call setState.

  • Smart Heuristics ( O(n) Complexity )


Flow Diagram

Three step process Triggering, Rendering (which includes Diffing), and Committing. You can think of React as a kitchen where the components are chefs, and React itself is the waiter managing the orders.

The Trigger

  • Initial Render: When your application first starts up.

  • State Updates: When a component’s state changes (e.g., via useState), React schedules a re-render for that component and its children.

The Render Phase (Thinking)

During this phase, React "thinks" about what the UI should look like based on the new data.

  • Rendering: React calls your component functions to generate a new Virtual DOM tree—a lightweight JavaScript representation of your UI.

  • Diffing (Reconciliation): React compares this new tree with the previous version. It uses a diffing algorithm to identify the minimal set of changes required (e.g., "only the color of this button changed").

  • Important: This phase is "pure"—it doesn't touch the real browser DOM and can be paused or restarted by React to prioritize urgent user interactions.

The Commit Phase (Doing)

Once the "thinking" is done, React moves to the commit phase to apply the changes.

  • DOM Manipulation: React applies the calculated updates to the Real DOM. If it’s the initial render, it uses appendChild(); for updates, it modifies existing nodes as efficiently as possible.

  • Side Effects: After the DOM is updated, React runs lifecycle methods like componentDidUpdate or hooks like useEffect.

  • Stability: Unlike the render phase, the commit phase in synchronous and non-interruptible to ensure the user doesn't see a partially updated UI.


Conclusion

By reducing the "work" the browser has to do, React keeps the UI responsive even as application complexity grows.