How React Works Inside the Browser Pipeline

Virtual DOM, reconciliation, state batching, and concurrent rendering — explained in terms of the six browser pipeline steps every frame goes through.

10 min read
React
Performance
Browser
JavaScript

TABLE OF CONTENTS
How React Works Inside the Browser Pipeline

When you use React, you're not bypassing the browser's rendering pipeline — you're working within it. React's entire design is shaped by the six steps the browser runs on every frame: parse, style, render tree, layout, paint, composite. Understanding where React sits inside that sequence explains why it works the way it does.

If you haven't read How the Browser Renders a Page yet, that's the foundation this builds on.


The Problem React Is Solving

Every DOM change is expensive. Touching the DOM — adding a node, changing a class, updating text — can trigger layout recalculation, repaint, or both. In a complex UI where dozens of things update in response to a single user action, naively updating the DOM for each change would hammer the browser's layout engine.

React's answer is to batch DOM work: collect all the changes, figure out the minimum set of actual DOM mutations needed, then apply them in one go.


The Virtual DOM

React maintains an in-memory copy of the DOM called the Virtual DOM (vDOM). It's a plain JavaScript object tree — cheap to create, cheap to traverse, cheap to throw away.

When your component returns JSX, React doesn't touch the real DOM. It builds or updates the vDOM tree instead.

This tree lives entirely in JavaScript — it never touches Steps 3–6 of the browser pipeline until React decides to flush changes.


Reconciliation: Finding the Diff

When state or props change, React re-runs the component function to get a new vDOM tree, then diffs the new tree against the previous one. This process is called reconciliation.

React's diffing algorithm makes two assumptions to keep it O(n) instead of O(n³):

  1. Elements of different types produce completely different trees — React tears down and rebuilds rather than patching.
  2. The key prop is used to identify which list items are the same across renders.

The output of reconciliation is a list of DOM mutations — the minimum changes needed to bring the real DOM in sync. React then applies these in a single batch, which is one layout pass, one repaint.


Where React Runs in the Pipeline

React's reconciler runs on the main thread, inside the JavaScript execution slot of the browser's frame loop. It's not magic — it competes for the same 16.67ms budget as everything else.

Browser Frame
│
├── [Input events / rAF callbacks]
├── [JavaScript — React reconciler runs here]
│     ├── Re-run component functions → new vDOM
│     ├── Diff new vDOM vs old vDOM
│     └── Apply minimal DOM mutations
├── [Style recalculation]
├── [Layout]
├── [Paint]
└── [Composite]

This is why a React app can still jank. If reconciliation takes 80ms (a large tree, expensive render functions), the frame misses its deadline and the user sees a dropped frame. The vDOM isn't free — it just costs less than touching the real DOM on every update.


State Batching: One Update, One Re-render

React batches multiple state updates within the same event handler into a single re-render, which means a single layout pass.

React 18 extended batching to cover setTimeout, Promise callbacks, and native event handlers — contexts where React 17 would flush each update separately.


React 18: Concurrent Rendering

React 18 introduced concurrent rendering — the ability to pause, resume, and abandon renders in progress. This is the biggest architectural change since React's original release.

The Problem It Solves

In React 17, once reconciliation started, it ran to completion. A large re-render on a slow device could block the main thread for 200ms — no input handling, no animation, no scroll.

How Concurrent Mode Works

React 18 breaks reconciliation into small units of work ("fibers") and checks after each unit whether the browser needs the main thread back. If a higher-priority task arrives (user input, an animation frame), React yields, lets the browser handle it, then resumes the render.

startTransition marks the results update as interruptible. While it's in progress, the input stays responsive. If the user types again before the results finish rendering, React throws the in-progress render away and starts fresh with the new query.


React Server Components

React Server Components (RSC) move component rendering to the server before the HTML is sent to the browser. The server sends a serialized component tree; the client hydrates it without re-running the component logic.

From the browser pipeline's perspective, RSC shifts work out of the JavaScript execution slot entirely for the initial load — the browser receives HTML and a compact payload describing the UI, rather than a JavaScript bundle that must run to produce HTML.


Practical Takeaways

SituationWhat's happening in the pipelineFix
Large list re-renders slowlyReconciliation takes too long, blocks main threadVirtualize the list (react-window) or use React.memo
Input lags during a heavy state updateMain thread is busy reconcilingWrap the heavy update in startTransition
Animation stutters after a state changeLayout/paint triggered by DOM mutations during animationMove animation to CSS transform; keep state updates separate
Full page re-render on every keystrokeToo many components re-renderingReact.memo, useMemo, stable references with useCallback

React is a layer on top of the browser pipeline, not a replacement for it. Every virtual DOM flush eventually becomes a real DOM mutation, which the browser must still process through layout, paint, and composite. React's job is to make those flushes as small and infrequent as possible — and with concurrent rendering, to make sure the work never blocks the thread for long enough to hurt the user.


Let's Connect

© 2026 Naveen Karthik // Built with React & MUI