5 min read
0%

Microtasks vs Macrotasks in React

Back to Blog
Microtasks vs Macrotasks in React

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 / await
  • queueMicrotask
  • MutationObserver

Macrotasks — one per event loop turn:

  • setTimeout / setInterval
  • requestAnimationFrame
  • MessageChannel
  • 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.


Canvas is not supported in your browser