React & Modern Frontend Frameworks

React Internals & Reconciliation

5 min read

Understanding how React works under the hood sets you apart from candidates who only know the API surface. Interviewers at Meta and Google specifically test these concepts.

Virtual DOM vs. Fiber Architecture

React does not manipulate the real DOM directly. Instead, it maintains a lightweight JavaScript representation of the UI.

Virtual DOM (React 15 and earlier):

  • React creates a tree of plain JavaScript objects representing the UI
  • On state change, React creates a new tree and diffs it against the previous one
  • The diff (reconciliation) identifies the minimal set of DOM operations needed
  • React applies these operations in a single batch

Fiber Architecture (React 16+):

  • React replaced the recursive, synchronous reconciliation algorithm with Fiber
  • Each component instance is represented by a "fiber" node in a linked list
  • Work can be interrupted, paused, and resumed — the key improvement
  • This enables prioritization: user interactions get higher priority than background updates
Fiber Tree (linked list structure):
  App
   ├── Header (sibling → Sidebar)
   │    └── Nav (child)
   ├── Sidebar (sibling → Main)
   │    └── Menu (child)
   └── Main
        └── Content (child)

The Reconciliation Algorithm

When state changes, React performs reconciliation — comparing the old and new fiber trees:

Rule 1: Different element types → tear down and rebuild

// Old: <div><Counter /></div>
// New: <span><Counter /></span>
// React unmounts Counter entirely and remounts a new instance

Rule 2: Same element type → update attributes only

// Old: <div className="old" title="x" />
// New: <div className="new" title="x" />
// React only updates className, keeps the DOM node

Rule 3: Keys identify list items across renders

// Without keys — React re-renders ALL items when list changes
<ul>
  {items.map(item => <li>{item.name}</li>)}
</ul>

// With keys — React knows exactly which items changed
<ul>
  {items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>

Why keys matter (interview favorite):

  • Without keys, React matches children by index
  • Inserting an item at the beginning shifts all indexes, causing every item to re-render
  • With unique keys, React can match items across renders and only update what changed
  • Keys should be stable, unique identifiers (database IDs), never array indexes for dynamic lists

Concurrent Features

React 18+ introduced concurrent rendering, built on Fiber:

useTransition

Marks a state update as non-urgent, letting React keep the UI responsive:

function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleChange(e) {
    // Urgent: update the input immediately
    setQuery(e.target.value);

    // Non-urgent: update results can be interrupted
    startTransition(() => {
      setResults(filterLargeDataset(e.target.value));
    });
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending ? <Spinner /> : <ResultsList results={results} />}
    </>
  );
}

useDeferredValue

Creates a deferred copy of a value that lags behind the current value:

function SearchResults({ query }) {
  // deferredQuery updates only when React has spare time
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;

  return (
    <div style={{ opacity: isStale ? 0.7 : 1 }}>
      <ExpensiveList query={deferredQuery} />
    </div>
  );
}

Key difference: useTransition wraps the state setter (you control when to transition). useDeferredValue wraps a value (the component consuming the value decides to defer).

React Compiler

The React Compiler (released separately from React 19) is a build-time tool that auto-memoizes components:

// Before React Compiler: manual memoization
const MemoizedChild = React.memo(({ data }) => {
  const processed = useMemo(() => expensiveCalc(data), [data]);
  const handler = useCallback(() => doSomething(data), [data]);
  return <div onClick={handler}>{processed}</div>;
});

// With React Compiler: write natural code
// The compiler automatically adds memoization where needed
function Child({ data }) {
  const processed = expensiveCalc(data);
  const handler = () => doSomething(data);
  return <div onClick={handler}>{processed}</div>;
}
// The compiler transforms this at build time to include
// the equivalent of memo, useMemo, and useCallback

Interview tip: When asked about React performance, mention the progression: manual React.memouseMemo/useCallback → React Compiler. Show awareness that the ecosystem is moving toward automatic optimization.

Next, we'll dive deep into hooks and state management patterns. :::

Quiz

Module 3: React & Modern Frontend Frameworks

Take Quiz