How React Virtual DOM works under the Hood

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(), executecomponentWillMount(), render the component, and finally triggercomponentDidMount()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()fromreact-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
useStateanduseEffect. Instead ofcomponentDidMount, code runs insideuseEffectduring 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
componentDidUpdateor hooks likeuseEffect.
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
componentDidUpdateor hooks likeuseEffect.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.