
Microtasks vs Macrotasks in React
JavaScript’s event loop separates work into microtasks (high priority, run before paint) and macrotasks (lower priority, one per event loop turn). React’s scheduler uses this to control update priority.
The Event Loop
Macrotask → Microtask queue (drain to empty) → Render → Next macrotask Microtasks — run between macrotasks, before browser paints:
Promise.then/awaitqueueMicrotaskMutationObserver
Macrotasks — one per event loop turn:
setTimeout/setIntervalrequestAnimationFrameMessageChannel- User input events (click, keydown)
React’s Scheduler
React uses MessageChannel (a macrotask) to yield to the browser between chunks of work:
const channel = new MessageChannel();
channel.port1.onmessage = performWork; // resumes as macrotask
function scheduleWork() {
channel.port2.postMessage(null);
} Why not setTimeout(fn, 0)? setTimeout has a minimum ~4ms delay when nested. MessageChannel fires immediately after the current macrotask — lower latency for the scheduler.
Promises and State Updates
Promises resolve as microtasks:
async function fetchAndUpdate() {
const data = await fetch('/api'); // await resumes as microtask
setState(data); // React 18: batched with other updates
} In React 18, state updates inside microtasks are automatically batched and flushed together.
setTimeout and Batching
setTimeout callbacks are macrotasks:
setTimeout(() => {
setState(1); // React 18: both batched
setState(2);
}, 0); React 18 batches these. React 17 would render twice.
requestAnimationFrame
requestAnimationFrame fires before the next paint — between macrotask and render in the loop:
requestAnimationFrame(() => {
// Good for animation frame updates — runs just before paint
setPosition(calculateNewPosition());
}); Avoid heavy computation here — it delays the paint.
useEffect vs Microtask Timing
useEffect(() => {
console.log('effect'); // runs after paint
}, []);
Promise.resolve().then(() => console.log('microtask')); // runs before paint
// Order: microtask → paint → effect Effects are scheduled via the scheduler (macrotask-ish). Never rely on effects running before Promise.then callbacks in the same event turn.









