// LOADING
// LOADING
// LOADING_ARTICLE
React developers often hear that the Virtual DOM makes React fast. That framing is a bit misleading — the Virtual DOM is a correctness mechanism, not purely a speed optimization. What actually makes React performant is knowing when to re-render and what to update in the real DOM. Getting that right starts with understanding reconciliation.
The Virtual DOM (vDOM) is an in-memory JavaScript representation of the real DOM tree. When your component returns JSX, React creates a lightweight object tree describing the intended UI — it does not touch the browser's DOM yet.
When state or props change, React creates a new vDOM tree and diffs it against the previous one. Only the differences are applied to the real DOM. Directly writing to the DOM is expensive (it triggers layout and paint), so batching and minimizing those writes matters.
React's diffing algorithm operates under two assumptions to keep it O(n) rather than the theoretical O(n³):
When React compares two trees and finds an element of a different type at the same position, it unmounts the old subtree entirely and mounts the new one. This is why swapping a <div> wrapper for a <section> will destroy and recreate all children — a subtle cause of state loss.
tsx// Before <div> <Counter /> </div> // After — Counter unmounts and remounts, losing its state <section> <Counter /> </section>
Keys allow React to match elements across renders when a list is reordered, added to, or shrunk. Without stable keys, React falls back to index-based matching, which can cause incorrect state assignment and unnecessary full remounts.
tsx// Bad: using index as key causes stale state when items reorder {items.map((item, i) => <Row key={i} data={item} />)} // Good: use a stable, unique identifier {items.map(item => <Row key={item.id} data={item} />)}
Using index as a key is fine only for static, non-reorderable lists. The moment items can be inserted, deleted, or rearranged, swap to a stable ID.
A component re-renders when:
useState or useReducer).This last point is often overlooked. If a parent component re-renders and you have no memoization, the entire subtree below it re-renders too — even if nothing relevant changed. For most apps, this is fine; React is fast enough that unnecessary re-renders are only worth addressing when profiling reveals actual jank.
Three tools exist to skip re-renders when inputs haven't changed:
React.memo wraps a component and skips re-render if props are shallowly equal.useMemo memoizes an expensive computed value.useCallback memoizes a function reference so it stays stable across renders.tsxconst ExpensiveList = React.memo(function ExpensiveList({ items }: { items: Item[] }) { return ( <ul> {items.map(item => <li key={item.id}>{item.name}</li>)} </ul> ); }); // Parent function Parent({ rawItems }: { rawItems: RawItem[] }) { const items = useMemo(() => rawItems.map(transform), [rawItems]); const handleClick = useCallback((id: string) => { // handle without recreating this function each render }, []); return <ExpensiveList items={items} onSelect={handleClick} />; }
Don't reach for these by default. Every useMemo and useCallback has a cost — the comparison on every render. Profile first, optimize second.
In React 18, state updates are automatically batched even inside setTimeout, Promise.then, and native event handlers. Previously, only updates inside React event handlers were batched. This means fewer re-renders out of the box, but it also means you should not rely on sequential state reads immediately after setState calls.
ts// React 18: both setCount and setFlag batched into one re-render fetch('/api/data').then(() => { setCount(c => c + 1); setFlag(true); // single re-render, not two });
The React DevTools Profiler tab shows you which components rendered in each commit and why. Look at:
Pair the Profiler with your knowledge of reconciliation and you can trace exactly where unnecessary work happens. This complements other performance work covered in posts like Core Web Vitals in 2026 and Caching Strategies: CDN, ISR, Redis.
key to force a full remount when you want React to start from scratch (e.g., resetting a form after submission).The Virtual DOM and reconciliation are React's way of computing the minimum set of DOM mutations needed. Understanding the algorithm — element type comparisons, key-based list matching, and automatic batching in React 18 — gives you a mental model for predicting render behavior and diagnosing performance issues accurately, instead of guessing. For deeper state management context, see Local vs Global State in React.