React can pause, resume, or entirely abandon a render pass, yet it applies the finished tree to the DOM in a single synchronous commit that never yields. Why must the commit phase be uninterruptible, and what would break if React yielded partway through it?
answer
- disposable work versus observable work
- the browser must not paint mid-update
- other scripts could read the DOM in the gap
- you cannot cheaply undo a DOM mutation
- concurrency applies to rendering, not applying
basics
~20 sRender work is pure and in memory, so stopping or discarding it is free. Commit mutates the real DOM, so yielding partway would let the browser paint a mixture of two states and expose a half-updated tree to refs, effects and other scripts.
solid answer
~50 sThe asymmetry comes down to whether the work is disposable. The render phase only computes: it produces JavaScript objects nobody outside React can observe, so React can stop between units, resume later, or throw the whole thing away with no visible consequence. The commit phase is where the results become real — nodes are inserted and removed, props are written, refs are attached, layout effects run. If React yielded in the middle of that, the browser could paint between two mutations of the same update, showing part of the old UI next to part of the new one, and any code that ran in that gap — an event handler, a resize observer, third-party script — would see a DOM that matches no single state. There is also no cheap rollback for DOM mutation. So React keeps the commit short and atomic, which is why heavy commit-time work is expensive in a way heavy render work is not.
go deeper
Hold on to the contrast: rendering only computes, so it can stop anywhere, while committing changes the real page and therefore has to finish in one go.
Explain the two concrete hazards of a split commit — the browser painting a mixture of two states, and other scripts reading a DOM that matches neither tree.
Show the operational consequence: commit cost sits on the frame's critical path and no concurrent feature slices it, so you optimise by changing fewer nodes and keeping layout effects small.
Own the boundary as a design rule for the team: put reversible, expensive computation where the scheduler has leverage, and treat everything observable as an atomic section whose size you budget deliberately.
## Two kinds of work, two different rules React's update pipeline has a render phase that computes and a commit phase that applies. The scheduling rules differ because the *nature* of the work differs: - Render-phase work is pure computation over heap objects. Nothing outside React can see it. It can be paused between units, resumed, restarted from scratch, or discarded, and none of those choices are observable. - Commit-phase work is effectful and observable. It changes the document, attaches refs, runs layout effects. Once a mutation lands, the page is different for everyone: the browser's layout engine, other scripts, assistive technology, the user's eyes. Interruptibility is affordable only for the first kind. ## What a yield in the middle of commit would cost **A torn frame.** Yielding means returning to the browser, and the browser may paint. If it paints when half of an update's mutations have been applied, the user sees a frame that corresponds to no state the application was ever in — a new header above a stale list, a spinner beside already-loaded content. React's central guarantee is that the visible UI is a function of one state snapshot; a split commit breaks exactly that. **Observers seeing an inconsistent tree.** Yielding also lets other JavaScript run: a click handler, a resize or intersection observer callback, a third-party widget's own timer. Any of them can read the DOM, and in that window the DOM matches neither the old tree nor the new one. Code that assumes an internally consistent document — measuring related elements, walking a list it expects to be complete — would read nonsense intermittently. **No rollback.** Abandoning a render costs a dereference: drop the in-progress tree and let it be collected. There is no equivalent for the DOM. Removing nodes already inserted is not free, it fires observers, it destroys state held by real elements (scroll position, focus, media playback, third-party widgets), and by then refs may already point at nodes that were about to be discarded. React can only afford abandonment where abandonment is cheap. **Broken invariants for refs and layout effects.** The commit is precisely where React establishes the contract that the DOM matches the tree it just rendered. Layout effects and ref callbacks run inside the commit so that when your code reads a node or measures it, the whole update is already in place. If a yield could interleave, that guarantee would degrade to "the parts committed so far are in place" — which no measurement code could rely on. ## The consequence for how you write components Because commit is one synchronous block, everything React must do at commit time is on the critical path of the frame: - A commit touching a very large number of nodes is a long task, and no concurrent feature will slice it. Concurrency helps you *compute* a huge tree without blocking; it does not help you *apply* one. - Work you put in layout effects runs inside that block, before the browser can paint. A measurement plus a synchronous state update there is paid for in the same frame, deliberately, because that is what avoids a visible flash — but it is a cost you should keep small. - Passive effects are deliberately outside the block: React flushes them after the browser has had a chance to paint, so ordinary side effects do not extend the atomic section. The practical rule is that render cost is schedulable and commit cost is not, so a design that reduces how many nodes actually change is worth more than one that only makes rendering cheaper. ## How to answer this crisply Say the principle first — commit is atomic because DOM mutation is observable and irreversible, while render output is neither — then give one concrete failure: a paint between two mutations of the same update showing a mixture of two states. Then note the corollary that the "concurrent" in concurrent React applies to rendering only. ## A common misconception Candidates sometimes assume concurrent rendering means React spreads DOM updates across frames, and that a big list appears progressively because of it. It does not: the tree is applied all at once. Anything that appears progressively did so because separate updates committed separately — for example a streamed or suspended boundary revealing in its own commit — not because one commit was split.
- Given that the commit cannot be sliced, what do you actually do about a commit that is too slow?Reduce how many nodes change, not how fast they are computed. Render fewer elements at once — windowing a long list is the standard lever — keep keys stable so React updates in place instead of remounting subtrees, and avoid patterns that force whole branches to remount. Also keep layout effects light, since they run inside the same synchronous block and delay the paint.
- If commit is atomic, why does a page sometimes visibly update in stages?Because those are separate commits, not one split commit. React may commit an urgent update now and a transition's result later, and a suspended boundary reveals its content in its own commit once the data arrives. Each of those is individually atomic; what you are watching is a sequence of complete updates, not a partially applied one.
- Where do passive effects run relative to this atomic block, and why there?Outside it. Layout effects and ref attachment run inside the commit, before the browser paints, because measurement code needs the DOM already consistent. Passive effects are flushed afterwards, once the browser has had the opportunity to paint, so that ordinary side effects — subscriptions, logging, data fetching — do not lengthen the synchronous section and delay the frame.
saying these in an interview costs you the question
- Thinks concurrent React spreads DOM mutations across frames
- Says React could roll back a partially applied commit
- Believes a paint between mutations would be harmless
- Assumes commit is interruptible because rendering is
- Claims progressive appearance proves a commit was split