Explain React’s update pipeline: what a “re-render” really is (re-running component functions to produce a new element tree), what triggers it (state/props/context/parent renders), how reconciliation decides what changed, and why a re-render often results in zero DOM mutations. Include bailouts (Object.is state equality, React.memo/PureComponent), identity pitfalls (inline objects/functions, Context value identity), StrictMode dev behavior, and keys/remount gotchas.
When does React re-render a component, and when does it actually update the DOM?
Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.
Core idea
In React, a re-render means: React runs your component again to compute the next React element tree (the in-memory UI description).
Re-render ≠ DOM update. After rendering, React reconciles (matches old vs new trees) and only then commits the minimal DOM mutations if anything actually changed.
Trigger | What happens | Important nuance |
|---|---|---|
State update (useState/useReducer) | Schedules a render for that component | If the new state is |
Parent re-renders | Children are rendered by default | Even if props “look the same”, React will call the child again unless you use |
Prop identity changes | Child re-renders | Inline objects/functions create new references every render → looks like “props changed” to memo/shallow compare. |
Context Provider value changes | All consumers may re-render | Context is identity-based too. |
Key / type changes at a position | React remounts the subtree | This is bigger than a re-render: local state resets because React treats it as a different component. |
Phase | What React does | Rule of thumb |
|---|---|---|
Render phase | Runs component functions / class | Must be pure (no subscriptions, network calls, DOM writes). React may run it more than once in dev/StrictMode. |
Reconciliation | Compares old vs new elements (type + key + position) and decides what to keep/move/mount/unmount | Stable keys/types preserve state and reduce work; unstable identity causes extra work and bugs. |
Commit phase | Applies DOM mutations and runs effects/lifecycles | This is when the browser DOM can actually change; effects run after commit. |
import React from 'react';
const Child = React.memo(function Child({ onInc, config }) {
console.log('Child rendered');
return <button onClick={onInc}>Inc</button>;
});
export default function Parent() {
const [count, setCount] = React.useState(0);
// ❌ NEW function/object every render -> Child sees "new props" -> re-renders
// const onInc = () => setCount(count + 1);
// const config = { step: 1 };
// ✅ Stable identities -> Child can skip renders via React.memo
const onInc = React.useCallback(() => setCount((c) => c + 1), []);
const config = React.useMemo(() => ({ step: 1 }), []);
console.log('Parent rendered');
return (
<div>
<p>Count: {count}</p>
<Child onInc={onInc} config={config} />
</div>
);
}
What this example proves
1) Updating count re-renders Parent.
2) By default, children render too.
3) React.memo lets a child bail out if props are shallow-equal, but only if you keep prop identities stable (callbacks/objects).
Common misconception | Correct framing |
|---|---|
“React re-renders the whole app.” | React schedules work for the affected part of the tree. But parent renders do propagate unless you add bailout boundaries (memo) and keep identities stable. |
“Virtual DOM diff always computes the minimal edit.” | Reconciliation is heuristic and optimized for common UI patterns. With good keys/types, it’s very efficient; with bad keys/identity churn, it can do extra work. |
“If a component re-rendered, the DOM changed.” | Not necessarily. If the computed output is the same (or the changed parts don’t affect the DOM), commit can be a no-op. |
“useCallback/useMemo always improves performance.” | They help only when they prevent real work (rerenders of expensive children, expensive computations). Overuse adds complexity and overhead. |
Optimization lever | What it actually reduces | Best use |
|---|---|---|
Colocate state (move it down) | The number of components affected by updates | Keep state as close as possible to where it’s used, so fewer parents re-render. |
Split components | The size of the rerendered subtree | Separate “fast-changing” parts from “mostly-static” parts. |
React.memo / PureComponent | Re-rendering of child components | Pure/presentational children with stable props. |
Stable prop identities | False-positive “prop changed” signals | Avoid inline objects/functions when they’re passed to memoized children. |
Memoize Context value | Broad consumer rerenders | Wrap provider value creation in |
Two important gotchas
• StrictMode (dev): React may intentionally run render logic more than once to surface unsafe side effects. Don’t treat “double logs” in dev as production behavior.
• Keys/type changes: Changing a component’s key or swapping wrappers (e.g., Fragment ↔ div) can cause a remount (state reset), not just a re-render.
React re-renders when state/props/context might have changed. A re-render is “recompute the next UI tree”, not “mutate the DOM”. React then reconciles and commits only necessary DOM changes. Unnecessary re-renders usually come from unclear state ownership, unstable prop identities (inline objects/functions), broad context updates, or bad keys.