- React
- State Mechanics, Batching, and Lifecycle Transitions
- State Updates & React 18 Automatic Batching
State Updates & React 18 Automatic Batching
Batching groups multiple state updates into a single re-render to optimize performance. In React 18+, automatic batching batches updates everywhere—including promises, setTimeout, and native event handlers—unless explicitly opted out via flushSync.
Key Learning Objectives
Understand the performance rationale behind batching multiple state updates.
Explain how React 18 automatic batching differs from React 17 synthetic-event-only batching.
Identify how promises, setTimeout, and fetch callbacks are batched in React 18.
Use flushSync responsibly when synchronous DOM measurement is required.
The Interview Problem & Context
What is logged to the console when simulating state batching inside an asynchronous Promise in React 17 vs React 18 vs flushSync?
1// Simulating render counts for 2 consecutive state updates inside an async callback2function simulateBatching() {3 // React 17: Async callbacks (setTimeout, Promise) did NOT batch -> 2 renders4 const react17AsyncRenders = 2;56 // React 18: Automatic Batching groups async state updates -> 1 render7 const react18AsyncRenders = 1;89 // React 18 flushSync: Forces immediate synchronous DOM flush -> 1 render per call -> 2 renders10 const flushSyncRenders = 2;1112 return `${react18AsyncRenders} ${react18AsyncRenders} ${flushSyncRenders}`;13}1415console.log(simulateBatching());
Predict Console & Render Output
1 1 2
2 2 2
1 2 1
0 1 2
Execution Trace
Step 1 of 3 (Line 4)React 18 batches async state updates into a single render pass.
Deep Architectural Explanation
React 18 Automatic Batching
Batching is when React groups multiple state updates into a single re-render pass for better performance.
The History: React 17 vs React 18
- React 17 & Older: Only batched updates inside React event handlers (like
onClick). Updates insidesetTimeout,Promise.then,fetchcallbacks, or native event listeners triggered separate renders for each setState call. - React 18: Introduces Automatic Batching. Updates inside promises, timeouts, and native event handlers are all automatically batched into a single render pass out-of-the-box when using
createRoot.
Opting Out with flushSync
If you need to synchronously read from the DOM immediately after updating state (e.g., scroll a container to a newly added element), React provides flushSync from react-dom:
import { flushSync } from 'react-dom';
flushSync(() => {
setItems(prev => [...prev, newItem]);
});
// The DOM has been updated synchronously!
listRef.current.scrollTop = listRef.current.scrollHeight;Note: flushSync hurts performance because it forces a synchronous render and browser layout pass. Use it sparingly.
Common Interview Traps & Pitfalls
Assuming React 17 batched updates inside fetch() callbacks.
Using flushSync everywhere out of fear that state will not update in time.
Relying on state updates occurring synchronously in unit tests without act().
Senior Interviewer Follow-Up Probes
How does React 18 know which updates belong to the same batch in microtask queues?
Why does ReactDOM.createRoot enable automatic batching, while legacy ReactDOM.render does not?
How does flushSync interact with Concurrent React transitions?
Frequently Asked Interview Questions
Why was automatic batching not supported in React 17?
In React 17, batching was tied to the SyntheticEvent call stack. Once execution yielded to the browser event loop (such as in an async callback), React lost execution context and had to render immediately.
When should you use flushSync?
Only when you must immediately measure or manipulate the DOM based on new state, such as scrolling to the bottom of a chat window or measuring an element height before an animation.
Does automatic batching work with legacy ReactDOM.render?
No. Automatic batching is an exclusive feature of the React 18 createRoot API; applications using the legacy ReactDOM.render retain React 17 batching behavior.
Can you batch state updates across different components?
Yes. If multiple components have their state updated within the same event or asynchronous callback, React 18 batches all of them into a single coordinated re-render pass.
