React's render phase visits each node of its work tree twice — once on the way down, in what its source calls beginWork, and once on the way back up, in completeWork. What does React do at each of those two visits, and why is the upward visit needed at all?
answer
- down to discover, up to assemble
- calling the component happens on the way down
- DOM nodes exist before they are inserted
- children must exist before a parent can finish
- sibling next, otherwise finish the parent
basics
~20 sOn the way down, React runs the component and reconciles the elements it returns into child nodes. On the way up, it finishes each node — for host elements it creates the detached DOM instance and appends its finished children — then moves to the sibling or the parent.
solid answer
~50 sDescending, React does `beginWork` for a node: it runs the component to get the elements it returns, reconciles those against the previous children to create or reuse child nodes, and then continues into the first child. When a node has no children left to process, React does `completeWork` on the way back up: for a host element that means creating the actual DOM instance off-document, setting its props and appending the already-completed children to it, then bubbling a summary of pending work to the parent. The loop then moves to the node's sibling, or up to the parent. The upward visit exists because a parent's work can only be finished once its children exist — building the detached subtree bottom-up means the commit can insert one prepared subtree instead of mutating the live document repeatedly.
go deeper
Know the direction of each visit: going down React runs components and works out their children, coming back up it finishes each node before moving to the next sibling.
Explain the dependency that forces two passes — children are discovered top-down, but a parent's DOM node can only be assembled once its children exist — and that host instances are built detached.
Draw the consequence for commit cost: a mounted subtree is assembled off-document and inserted once, so commit-time mutations on the live tree stay minimal and layout invalidation is bounded.
Own the design point: splitting node processing into small, self-describing half-visits is what gives the loop a yield granularity at all, and it fixes the floor at one component call.
## The shape of the walk React's render phase is a depth-first traversal, but it is expressed as a loop over single units of work rather than as recursion, and each unit is one half of a node's processing. Think of it as: go down as far as you can, finish what you touched on the way back up, and step sideways to siblings when there is nowhere further down. Concretely, processing one unit either produces the next node to go *into* (a child) or, when there is no child, finishes the current node and produces the next node to go *to* (a sibling, or the parent, which then finishes in turn). ## The downward visit: beginWork On the way down, React does the work that produces children. For a function component this means calling your component function with its props. That call is where hooks read and write their state and where you return elements. React then takes the returned elements and reconciles them against the children that existed previously, producing the child work nodes — reusing an existing one when the element type matches at that position, or creating a new one when it does not. This visit can also decide to do very little. If neither props nor state changed for a subtree, React can skip re-running the component and clone the existing children instead — the bail-out path. Either way, the downward visit ends by handing the loop the first child to process next. ```js // Conceptual: one unit of work either descends or finishes-and-steps-sideways. function performUnitOfWork(node) { const child = beginWork(node); // run component, reconcile children if (child !== null) return child; // go down return completeUnitOfWork(node); // finish, then sibling or parent } ``` ## The upward visit: completeWork On the way back up, React finishes each node it has fully descended through. For a host element — a `div`, an `input`, a real DOM tag — this is where React creates the actual DOM node, sets its initial attributes and properties from the props, and appends the children that were already completed beneath it. Critically, all of this happens on a node that is not in the document yet: the subtree is assembled detached, off-screen, invisible to layout and to anything observing the page. For a component that renders no host output, completing is mostly bookkeeping: React bubbles up the flags recording what the commit will have to do for that subtree, so the parent knows there is work below it without re-walking the whole tree later. When a node is complete, the loop tries its sibling. If there is no sibling, it completes the parent, and so on upward until it reaches the root — at which point the render pass is finished and a complete tree is ready to commit. ## Why two visits and not one The upward visit exists because of a dependency direction. Children cannot be known before the parent runs — you have to call the component to find out what it renders — so *that* work must happen top-down. But a parent's DOM node cannot be fully assembled before its children exist, so *that* work must happen bottom-up. One pass cannot satisfy both directions; hence down for discovery, up for assembly. The payoff shows at commit time. Because the new subtree was built detached, the commit for a newly mounted branch can insert one already-populated node rather than performing a mutation per element on the live document. Fewer touches on the live tree means less style and layout invalidation while the browser is watching. ## Why splitting it this way matters for interruptibility The split also gives the work loop its granularity. Each half-visit is small and self-contained, and the state that says "here is where I am" lives in the node itself rather than in a call frame, so React can stop between any two units and resume from the saved pointer. That also sets the hard limit on how finely React can yield: one call of your component function is one unit, and React cannot stop inside it. ## What not to confuse this with Neither visit puts anything into the document, runs effects, or attaches refs — all of that is commit-phase work. Creating a detached DOM instance during the upward visit is not the same as inserting it. And `beginWork` running your component is not the same as your component's output being final: React may discard the whole in-progress tree before it ever commits.
- If the DOM element is created during the render phase, what is left for the commit phase to do for a newly mounted subtree?Insertion and everything observable. The render phase produces a detached, fully populated subtree; the commit places it into the document, then attaches refs and runs layout effects, and later flushes passive effects. Nothing before that point is visible to the page, to layout, or to other scripts — which is what lets React abandon a render pass safely.
- Does the downward visit always call the component function?No. If props and state for that node are unchanged and no context it reads has changed, React can bail out: it skips calling the component and reuses the existing children. That is why an update at the root does not necessarily re-run every component beneath it, and why keeping props referentially stable can prune whole subtrees from the pass.
saying these in an interview costs you the question
- Thinks DOM nodes are only created in the commit phase
- Believes the render phase inserts elements into the document
- Says effects or refs run during beginWork or completeWork
- Describes the walk as pure recursion with no upward step
- Assumes every node's component function runs on every pass