Virtual DOM vs React DOM: Key Differences in React's Architecture

The virtual DOM is an in-memory JavaScript representation of the UI managed by React's reconciler, while React DOM is the concrete renderer that translates those changes into real browser DOM mutations.

When discussing the virtual DOM vs React DOM in the facebook/react codebase, it is essential to understand that these are two distinct architectural layers. The virtual DOM handles the abstract representation and diffing logic, while React DOM serves as the web-specific host renderer that applies updates to the actual browser document.

What Is the Virtual DOM?

The virtual DOM is an in-memory JavaScript representation of the UI, built from Fiber objects. It exists purely in memory and never touches the browser directly.

Implemented in the React Reconciler package (packages/react-reconciler), the virtual DOM constructs a tree of Fiber nodes from JSX and React elements. Its primary responsibilities include building this tree, performing the reconciliation algorithm to compute minimal changes, and scheduling work across synchronous, asynchronous, and concurrent modes.

The core work loop that manages this process lives in packages/react-reconciler/src/ReactFiberWorkLoop.js. According to the facebook/react source code, the performConcurrentWorkOnRoot function drives the reconciliation process:

// packages/react-reconciler/src/ReactFiberWorkLoop.js
export function performConcurrentWorkOnRoot(root, didTimeout) {
  // …reconcile fibers, generate side‑effects
}

Because it is pure JavaScript, the virtual DOM layer remains platform-independent. It can be used by any host environment including web, native, or server-side rendering contexts.

What Is React DOM?

React DOM is the concrete renderer that knows how to translate virtual DOM changes into real browser DOM mutations. Unlike the platform-agnostic reconciler, React DOM is specific to the web host environment.

Implemented in the React DOM package (packages/react-dom), this layer receives the list of changes from the reconciler and maps React elements and Fibers to actual DOM nodes. It handles the application of updates, insertions, removals, and event listeners.

The public entry points reside in packages/react-dom/src/shared/ReactDOM.js, while modern root creation lives in packages/react-dom/src/client/ReactDOMClient.js. The createRoot function exemplifies how React DOM initiates the rendering process:

// packages/react-dom/src/client/ReactDOMClient.js
export function createRoot(container) {
  // …create a root and schedule initial work
}

React DOM also exposes critical APIs like ReactDOM.render, createRoot, hydrate, and flushSync for synchronous updates.

Key Differences Between Virtual DOM and React DOM

Understanding the distinction between these layers reveals React's modular architecture:

  • Platform Independence: The virtual DOM is pure JavaScript and host-agnostic, usable by React Native, React Three Fiber, or custom renderers. React DOM is strictly the web implementation that interacts with document and window objects.

  • Responsibilities: The virtual DOM builds Fiber trees and performs diffing algorithms. React DOM consumes the resulting side-effects and performs actual DOM mutations via appendChild, setAttribute, and similar APIs.

  • Location: Virtual DOM logic resides in packages/react-reconciler, while React DOM code lives in packages/react-dom.

  • Extensibility: The reconciler is generic; custom renderers implement their own host-config. React DOM represents the official web host-config, replaceable by other hosts like react-native.

How They Work Together: The Rendering Flow

The interaction between virtual DOM and React DOM follows a precise pipeline:

  1. Entry Point: ReactDOM.render or createRoot (from packages/react-dom/src/client/ReactDOMClient.js) creates a root and hands the element tree to the reconciler.

  2. Reconciliation: The reconciler builds the virtual DOM (Fiber tree) and runs the work loop in packages/react-reconciler/src/ReactFiberWorkLoop.js.

  3. Diffing: After comparing the new and previous virtual trees, the reconciler produces a list of side-effects (placements, updates, deletions).

  4. Commit Phase: React DOM consumes these effects in packages/react-dom/src/shared/ReactDOM.js through functions like commitWork, performing the actual browser DOM mutations.

  5. Flushing: Updates are batched and flushed via the browser's event loop or flushSync from packages/react-dom/src/shared/ReactDOMFlushSync.js for synchronous guarantees.

This separation allows the heavy diffing logic to remain independent from host-specific mutation code, enabling React's declarative UI model across multiple platforms.

Code Example: From JSX to Browser DOM

Consider a standard React application:

// index.js
import React from 'react';
import {createRoot} from 'react-dom/client';

function Counter() {
  const [count, setCount] = React.useState(0);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Clicked {count} times
    </button>
  );
}

const root = createRoot(document.getElementById('root'));
root.render(<Counter />);

Underlying execution flow:

  • createRoot instantiates a root Fiber in packages/react-dom/src/client/ReactDOMRoot.js.
  • root.render passes the JSX-generated React elements to the reconciler.
  • performConcurrentWorkOnRoot builds the virtual DOM and detects changes.
  • React DOM's commitWork applies the mutations to the real button element.

Summary

  • The virtual DOM is an in-memory Fiber tree managed by the React Reconciler (packages/react-reconciler), handling diffing and scheduling without touching the browser.
  • React DOM is the web-specific renderer (packages/react-dom) that translates virtual DOM changes into actual DOM mutations.
  • The reconciler is platform-agnostic, while React DOM is the host-specific implementation for browsers.
  • Key files include ReactFiberWorkLoop.js for virtual DOM work and ReactDOM.js / ReactDOMClient.js for DOM operations.

Frequently Asked Questions

Is the virtual DOM the same as React DOM?

No. The virtual DOM is an abstract, in-memory representation of the UI constructed from Fiber nodes, while React DOM is the concrete renderer that writes those changes to the browser's actual DOM. They exist in separate packages (react-reconciler versus react-dom) within the facebook/react repository.

What package contains the virtual DOM implementation?

The virtual DOM implementation resides in packages/react-reconciler, specifically within files like ReactFiberWorkLoop.js and ReactInternalTypes.js. This package defines the Fiber architecture and reconciliation algorithm that powers React's diffing capabilities.

How does React Native fit into this architecture?

React Native replaces React DOM with its own host renderer while using the same virtual DOM layer. The reconciler (react-reconciler) remains unchanged, but instead of react-dom mutating HTML elements, React Native's renderer translates changes into native platform UI components (iOS/Android native views).

Where does the reconciliation algorithm run?

The reconciliation algorithm runs entirely within the virtual DOM layer inside packages/react-reconciler/src/ReactFiberWorkLoop.js. The performConcurrentWorkOnRoot function orchestrates this process, determining what has changed before handing a list of effects to React DOM for actual DOM manipulation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →