React splits an update into a render phase and a commit phase. Walk through what React does during the commit phase, in order, and say where the browser's paint fits into that sequence.
answer
- render decides, commit applies
- three sub-phases, one synchronous pass
- snapshot, mutate, then layout
- paint happens after the pass returns
- passive effects are scheduled, not committed
basics
~20 sReact commits in three sub-phases: it takes pre-mutation snapshots, then mutates the DOM and detaches old refs, then attaches new refs and runs layout effects. The browser paints after that, and useEffect callbacks flush later.
solid answer
~40 sThe render phase only computes what the tree should look like; it touches no DOM. The commit phase applies that result in one synchronous pass, in three sub-phases. **Before mutation**: React reads the old DOM while it is still intact — this is where a class's `getSnapshotBeforeUpdate` runs, and where `useInsertionEffect` callbacks fire so a styling library can inject rules before anything is measured. **Mutation**: React inserts, updates and deletes DOM nodes, detaches refs belonging to removed or replaced nodes, and runs the cleanup functions of layout effects. **Layout**: the DOM is now the new DOM, so React attaches refs and runs `useLayoutEffect` bodies plus `componentDidMount`/`componentDidUpdate`. Only when that whole pass returns does the browser get a chance to paint. Passive `useEffect` callbacks are scheduled separately and normally run after that paint.
code
jsx · 8 linesimport { useEffect, useLayoutEffect } from 'react';
export default function Order() {
console.log('1: render phase');
useLayoutEffect(() => console.log('2: layout effect, before paint'));
useEffect(() => console.log('3: passive effect, after paint'));
return null;
}go deeper
Be able to say that React first works out what the UI should be and only then touches the DOM, and that effects run after the DOM has been updated, not during rendering.
Name the three commit sub-phases and what each one does — snapshot before mutation, DOM writes and ref detachment during mutation, ref attachment and layout effects afterwards — and place the browser's paint after the whole pass.
Show that you use this timeline when diagnosing real problems: a flash of unstyled or mispositioned content, a slow interaction traced to heavy work sitting inside the commit, or a ref read at the wrong moment.
Own the budget argument: everything placed inside the commit pass is on the critical path of every frame, so establish team conventions for what may block paint and what must be deferred to passive effects or a transition.
## Two phases for one update An update in React 19 has two halves with very different rules. The **render phase** calls your component functions to work out what the UI should be. It produces a new work-in-progress tree of fibers and touches nothing outside React — no DOM writes, no observable side effects — which is exactly what lets React pause, restart or throw that work away. The **commit phase** takes the finished tree and makes the real DOM match it. It is a single synchronous pass: once it starts, it runs to completion, and the browser never sees a half-applied tree. The commit pass is internally split into three sub-phases, and almost every timing question about React reduces to knowing which sub-phase something belongs to. ## Sub-phase 1: before mutation At this point the DOM still shows the *previous* UI. That makes it the only safe moment to read something about the old screen that is about to be destroyed — the classic example is capturing a scroll position before a list grows. In a class component, `getSnapshotBeforeUpdate` runs here and its return value is handed to `componentDidUpdate`. This is also where React fires `useInsertionEffect`, the hook intended for CSS-in-JS libraries: it runs before any DOM mutations so injected `<style>` rules exist before anything is inserted or measured. ## Sub-phase 2: mutation This is where the DOM actually changes. React walks the fibers flagged during render and performs the insertions, attribute and text updates, and deletions. Two things happen alongside those writes: - **Refs are detached.** For a node being removed or replaced, React sets the ref to `null` (or calls the callback ref with `null`) here, so a ref never points at a node that has already left the document. - **Layout-effect cleanups run.** The cleanup returned by a `useLayoutEffect` from the previous commit runs in this sub-phase, before the new one is created. Between mutation and layout, React swaps which tree it considers current, so unmount code still sees the old tree while mount code sees the new one. ## Sub-phase 3: layout The DOM is now fully updated but the browser has not painted yet. React attaches refs to their new nodes and runs the "layout" work: `useLayoutEffect` bodies, `componentDidMount` and `componentDidUpdate`. Because paint has not happened, code here can read geometry and even call `setState`; React will render and commit again synchronously before yielding, so the user never sees the intermediate frame. ```jsx useLayoutEffect(() => { // DOM is updated, nothing has been painted yet const h = boxRef.current.offsetHeight; setHeight(h); // re-render + re-commit happen before paint }, []); ``` That power is also the cost: everything you do here sits on the critical path of the frame. ## Then the browser paints When the commit pass returns, control goes back to the browser, which performs style, layout and paint. This is the boundary that the whole topic turns on: work scheduled *inside* the commit blocks the pixels; work scheduled *after* it does not. ## Passive effects `useEffect` callbacks are deliberately **not** part of the commit pass. React schedules them and they normally run after the browser has painted, which is why they are called passive effects: a slow data fetch or subscription setup cannot delay the first frame. The guarantee React gives is not "a paint definitely happened" but "pending effects are flushed before React starts the next render" — if another update arrives quickly, React will flush the outstanding effects first so you never see effects from two commits interleaved. ## Why the order is what it is Each rule falls out of one constraint: an effect or lifecycle must see a consistent DOM. - Snapshots must be read before mutation, or the old DOM is gone. - Refs must be detached during mutation, or a ref could outlive its node. - Refs must be attached before layout effects, or `ref.current` would be `null` in code whose entire purpose is to touch the node. - Layout effects must run before paint, or a measured-then-corrected layout would flash. - Passive effects must run after paint, or every subscription would cost you a frame. ## What an interviewer is really testing The follow-up is almost always "so why does `useLayoutEffect` block paint and `useEffect` not?" If you can place both on this timeline — one inside the synchronous commit pass, one on a scheduled callback afterwards — you have answered it without memorising anything.
- Where in that sequence does a state update performed inside a layout effect get processed?Immediately, before the browser paints. React finishes the layout sub-phase, sees the pending update, and synchronously renders and commits again in the same frame. That is what makes measure-then-adjust patterns flicker-free, and also why a layout effect that updates state on every commit can lock up the frame.
- Why can React interrupt render work but not the commit pass?Because render produces nothing the user can see: a half-finished work-in-progress tree can be discarded with no consequence. Commit mutates the real DOM, so pausing halfway would leave the document in a state that matches neither the old tree nor the new one, and the browser could paint that inconsistency.
- What is useInsertionEffect for, and who is expected to call it?It fires before any DOM mutations in the commit, which is precisely when a CSS-in-JS library needs to inject its `<style>` rules so that later layout reads see the correct styles. It is a library-level hook: application code has essentially no reason to call it, and refs are not attached when it runs.
Render is drafting the change list; commit is the maintenance crew that walks in and applies every change with the lights off, and the lights only come back on once they leave the room.
saying these in an interview costs you the question
- Says the DOM is updated during the render phase
- Claims useEffect callbacks run before the browser paints
- Thinks the browser can paint midway through a commit
- Believes refs are attached while the component function runs
- Treats commit as just another name for re-rendering