Automatic Batching: Microtask Grouping Across Asynchronous Boundaries
- Last updated
- Prerequisites:
- React useState / state update queues
- JavaScript Event Loop and Microtask Queue
- react
- automatic-batching
- event-loop
- microtasks
- react-18
- performance
Read at your depth
The practical view
In React 18+, **Automatic Batching** groups multiple state updates (`setState`) into a single re-render pass, regardless of where the updates originate. In React 17, batching only worked inside native React event handlers. In React 18+, updates inside `fetch()` callbacks, `setTimeout`, `Promise.then`, or native DOM listeners are automatically batched together into one render.
The same idea in other frameworks
Automatic batching via microtask update queues / `flushSync()` escape hatch.Zoneless change detection coalescing multiple signal writes into one tick.Vue reactivity scheduler batching component watcher jobs in a microtask queue.Svelte 5 scheduler flushing state changes in a microtask batch.Legacy vs modern
Un-batched Async Updates (React 17) vs Automatic Batching (React 18+)
In React 17, updating state inside a fetch callback triggered 2 separate renders; React 18+ automatically batches them into 1 render.
fetch().then(() => { setCount(c => c+1); setFlag(f => !f); }); // ❌ 2 re-renders in React 17fetch().then(() => { setCount(c => c+1); setFlag(f => !f); }); // ✅ Exactly 1 re-renderInterview gotchas
Context
Interviewers test your knowledge of escape hatches like `flushSync`.
The mechanical answer
Wrap the state update in `ReactDOM.flushSync()`: `flushSync(() => { setCount(c => c + 1); });`. `flushSync` forces React to immediately execute the render and commit phases synchronously before returning, which is useful when reading DOM layout measurements (like scroll position) immediately after a state change.
Trap
`flushSync` degrades rendering performance significantly; only use it when strictly necessary for immediate DOM geometry calculation.
Context
Checks migration requirements for React 18 concurrent features.
The mechanical answer
Automatic batching is only enabled when using the `ReactDOM.createRoot()` API. If an application uses the legacy `ReactDOM.render()` entrypoint (supported for migration in React 18), React falls back to React 17 behavior where async callbacks are not batched.
Trap
Upgrading `package.json` to React 18 does not enable automatic batching unless you update `index.js` to use `createRoot`.