How React Works Internally: Virtual DOM, Reconciliation, and Rendering

What actually happens when we call setState?
You call a state setter. A moment later, the screen updates. Between those two events, React quietly runs an entire internal pipeline — building a new description of the UI, comparing it against the old one, figuring out the minimum set of real changes needed, and finally touching the actual browser.
Most developers use this pipeline every day without ever seeing it. This guide opens it up — not the deep Fiber scheduler internals, but the mental model every React developer should carry: the Virtual DOM, reconciliation, diffing, and the full journey from a state update to pixels changing on screen.
1. Why React Needed a New Rendering Approach
Problems with direct DOM manipulation
Before React, updating a UI usually meant finding a DOM element and mutating it directly — and doing that correctly, consistently, across a large, frequently changing interface, becomes genuinely difficult to manage by hand.
Cost of frequent browser updates
Every direct DOM change can force the browser to recalculate layout and repaint pixels — real, measurable work. Applications making many small, uncoordinated DOM updates can trigger this work far more often than necessary.
UI complexity in modern applications
Modern interfaces update constantly — live counters, chat messages, dashboards — often from many independent components at once. Coordinating this by hand, imperatively, becomes its own significant source of bugs and wasted performance.
React’s approach to rendering
React’s answer: don’t touch the real DOM directly at all. Instead, describe the UI as a lightweight, in-memory structure (the Virtual DOM), compare it to the previous version, and let React apply only the real, minimal changes needed.

2. Understanding the Real DOM
What the DOM is
The DOM (Document Object Model) is the browser’s live, in-memory tree representation of your HTML — the actual structure the browser renders and that JavaScript can inspect or modify.
Browser representation of HTML
Every tag becomes a node in this tree, each carrying its own properties, styles, and children — a rich, heavyweight object, not a simple piece of text.
Why DOM operations are expensive
DOM nodes are far more complex than plain JavaScript objects — they carry layout information, styling, accessibility data, and more. Reading or writing to them, especially repeatedly, carries real overhead compared to working with plain in-memory data.
Reflows and repaints
A reflow (recalculating layout — positions and sizes) and a repaint (redrawing pixels) are two of the most expensive operations a browser performs. Certain DOM changes (like resizing an element) force a reflow, cascading recalculations to related elements as well.
Performance implications
Frequent, uncoordinated DOM writes — especially ones that force reflows — can visibly slow down an interface. This is precisely the cost React’s Virtual DOM approach is designed to minimize.

3. What is the Virtual DOM?
Virtual DOM concept
The Virtual DOM is a lightweight, plain JavaScript object representation of what the UI should look like — a tree of simple objects, not real, heavyweight DOM nodes.
JavaScript representation of UI
// Conceptually, a Virtual DOM node looks like:
{
type: "button",
props: { className: "btn", children: "Click me" }
}Creating, comparing, and discarding these plain objects is dramatically cheaper than doing the same directly on real DOM nodes.
Why React uses a Virtual DOM
It gives React a fast, in-memory space to figure out what changed before ever touching the actual, expensive real DOM — comparing plain JavaScript objects instead of live browser nodes.
Virtual DOM vs Real DOM

Benefits and limitations
The Virtual DOM makes comparing “what changed” cheap — but it isn’t magic, and it doesn’t eliminate real DOM updates entirely (a point revisited in Section 11’s misconceptions). It’s an optimization strategy, not a way to avoid touching the DOM altogether.
4. Initial Rendering Process
Component rendering
On first render, React calls each component function from the root down, collecting the JSX each one returns.
JSX transformation
As covered in earlier parts of this series, JSX compiles down to React.createElement() calls, which produce the plain Virtual DOM objects described above.
Virtual DOM tree creation
React assembles all these objects into a complete Virtual DOM tree — a full, in-memory description of the entire UI, before anything touches the real browser.
Real DOM generation
React then creates the actual, real DOM nodes matching that Virtual DOM tree, and inserts them into the page — this is what the user sees for the very first time.
What happens on first render

5. What Happens When State Changes?
State updates
Calling a state setter (setCount, for example) tells React: this component's data has changed, and its UI may now need to look different.
Component re-execution
React re-runs the component’s function with the new state value — this is the “re-render” discussed in earlier parts of this series.
New Virtual DOM creation
That re-run produces a brand-new Virtual DOM tree describing what the UI should look like now — entirely separate, in memory, from the previous tree.
React update cycle
React now holds two trees: the previous Virtual DOM (what was actually last rendered) and the new one (what should be rendered now) — setting up the comparison covered next.
Re-render process

6. Understanding Reconciliation
What reconciliation means
Reconciliation is the process React uses to compare the old Virtual DOM tree against the new one and determine exactly what actually changed.
Why React compares trees
Rather than blindly rebuilding the entire real DOM from scratch on every update (which would be simple but extremely wasteful), React compares trees specifically to find the smallest possible set of real changes needed.
Old tree vs new tree

Finding minimal changes
In the example above, reconciliation determines that only the h1's text actually changed — the surrounding div and the button didn't, so React won't touch them in the real DOM at all.
Efficient UI updates
This targeted approach — updating only what genuinely changed — is the core performance benefit reconciliation provides, especially as UI trees grow large and complex.
7. The Diffing Algorithm
Why diffing is needed
Comparing two arbitrary trees node-by-node, in the fully general case, is computationally expensive (technically, it can require far more comparisons than is practical at UI speed). React’s diffing algorithm uses heuristics — reasonable, practical assumptions — to make this fast enough for real-time UI updates.
Tree comparison process
React compares trees level by level, assuming that elements of a different type at the same position represent a genuinely different subtree — rather than trying to painstakingly compare every possible node pairing.
Element matching
If an element’s type changes at a given position (say, a div became a span), React assumes the entire subtree beneath it is different too, and rebuilds that section rather than trying to reuse pieces of it.

Component matching
For components, React uses type to decide whether to reuse an existing component instance (updating its props) or discard it and create a fresh one — which is also exactly why key (Section 9) matters so much for lists.
Optimizing updates
These heuristics aren’t a perfect, mathematically optimal diff — they’re a deliberate, practical tradeoff: fast enough to run on every update, in exchange for occasionally being slightly less “minimal” than a theoretically perfect diff could be.
8. Render Phase vs Commit Phase
Render phase responsibilities
The render phase is where React calls component functions, builds the new Virtual DOM tree, and performs reconciliation to figure out what changed. Critically, nothing on screen changes yet during this phase.
Commit phase responsibilities
The commit phase is where React actually applies the calculated changes to the real DOM — this is the only phase where the user’s screen is actually updated.
Why React separates these stages
Separating “figure out what changed” from “actually change it” lets React work efficiently and, in more advanced scenarios, even pause or adjust its work-in-progress calculations before anything real DOM-facing has happened — without the user ever seeing an incomplete, in-between state.
High-level rendering workflow

9. Keys and List Rendering
Why keys exist
When rendering a list of elements, React needs a reliable way to tell which item in the new list corresponds to which item in the old list — key is exactly that identifier.
Stable identity of elements
A stable, unique key (typically an item’s own ID, not its array index) lets React correctly match items across renders — even if their order changes, items are added, or items are removed.
{users.map(user => (
<UserCard key={user.id} user={user} />
))}List rendering performance
With correct keys, React can efficiently reuse existing DOM nodes for items that persist across a re-render, rather than tearing down and rebuilding the entire list.
Common key mistakes
Using the array index as a key is a frequent mistake — if the list’s order can change (items inserted, removed, or reordered), the index no longer reliably identifies the same logical item across renders, leading to subtle bugs like state or focus jumping to the wrong row.
Impact on reconciliation

Without stable keys, React can’t make this distinction reliably — and may unnecessarily discard and recreate DOM nodes (and any state they held) that didn’t actually need to change at all.
10. React Performance Optimization
Avoiding unnecessary re-renders
As covered in earlier parts of this series, a component re-renders when its own state changes, its props change, or its parent re-renders — the third case being the most common source of unnecessary work in a large tree.
Component splitting
Breaking a large component into smaller, focused pieces means a state change in one small piece doesn’t force an entire large section of UI to re-render along with it.
Memoization concepts
Memoization means caching a result so it doesn’t need to be recomputed (or, for components, re-rendered) when nothing relevant has actually changed.
React.memo
Wraps a component so React skips re-rendering it if its props haven’t changed — directly reducing wasted reconciliation and diffing work for that specific component.
useMemo
Caches the result of an expensive calculation between renders, recomputing only when its listed dependencies change — reducing repeated work within a render, rather than skipping the render itself.
useCallback (high level)
Caches a function’s reference between renders, which matters specifically when that function is passed as a prop into a React.memo-wrapped child — without it, a fresh function reference on every render would defeat that child's memoization.
(These three tools are covered in full depth, including their tradeoffs, in this series’s dedicated state-management article.)
11. Common Virtual DOM Misconceptions
Virtual DOM is not always faster
The Virtual DOM doesn’t make every single operation faster in isolation — creating and diffing extra JavaScript objects has its own cost. Its real benefit is avoiding unnecessary, expensive real DOM operations across an entire application, not winning every micro-benchmark against hand-optimized direct DOM code.
React still updates the real DOM
The Virtual DOM doesn’t replace real DOM updates — it’s a strategy for figuring out the minimal real DOM updates needed. The commit phase still genuinely touches the browser’s real DOM; React just works hard to touch as little of it as possible.
Diffing is an optimization strategy
React’s diffing algorithm uses practical heuristics, not a perfect, exhaustive comparison — it’s a deliberate engineering tradeoff between speed and absolute minimality, not a guarantee of the theoretically smallest possible update.
Understanding actual performance gains
The Virtual DOM’s real value is architectural: it lets developers write declarative UI code (describe the desired state, re-render freely) while React handles minimizing the expensive, real browser work behind the scenes — not a blanket claim that “Virtual DOM is always faster than any direct DOM manipulation.”

12. React Rendering Lifecycle
Bringing every piece together, from a state update to pixels on screen:
- Initial render — components run, Virtual DOM tree is built, real DOM is created and inserted
- State update — a setter function is called, scheduling a re-render
- Virtual DOM creation — the affected component (and its children) re-run, producing a new Virtual DOM tree
- Reconciliation — React compares the old and new trees to find what actually changed
- Commit — React applies exactly those changes to the real DOM
- Browser paint — the browser reflows and repaints as needed, and the user sees the updated UI

Final Takeaway
Every part of this pipeline exists to answer one question efficiently: given a new state, what’s the smallest possible set of real changes needed to make the actual DOM match it? The Virtual DOM gives React a cheap space to describe the desired UI. Reconciliation compares old and new descriptions. Diffing makes that comparison fast enough to run constantly, using practical heuristics rather than a perfect algorithm. The render and commit phases separate “figuring it out” from “actually doing it.” And keys give React the identity information it needs to do all of this correctly across changing lists. None of it replaces the real DOM — it’s simply the most efficient path React has found to it.
Frequently Asked Questions
Is the Virtual DOM unique to React?
> No — the general idea of diffing an in-memory UI representation before touching the real DOM has been used by other libraries and frameworks too. React popularized the term and the pattern widely, but it isn’t an exclusively React-only concept.
Does using React.memo, useMemo, or useCallback skip reconciliation entirely?
> Not exactly — React.memo can skip re-rendering (and therefore re-diffing) a component entirely if its props haven't changed. useMemo and useCallback don't skip rendering; they avoid recomputing a value or recreating a function reference within a render that still happens.
Why does React recommend against using array index as a key?
> Because the index doesn’t reliably represent a specific item’s identity if the list can reorder, insert, or remove items — leading React to potentially misidentify which item is which across renders, causing subtle state or UI bugs.
Does reconciliation guarantee the theoretically minimal set of DOM changes?
> No — it uses practical heuristics (matching by element type and position, or by key for lists) that are fast enough to run on every update, which is a deliberate tradeoff rather than a guarantee of mathematically perfect minimality.
Originally published by Mr Madhukar
Read the complete article on Medium with full formatting & reader responses.