HuntExams Academy logo
JavaScriptAdvanced#event-loop#async

What are microtasks and macrotasks?

Answer

Microtasks are promise reactions, queueMicrotask and MutationObserver callbacks; the queue is fully drained after each task. Macrotasks include setTimeout, setInterval, I/O and UI events, and only one runs per loop turn.

Example

setTimeout(() => console.log('macro'));
Promise.resolve().then(() => console.log('micro'));
// micro then macro

Detailed explanation

Microtasks and macrotasks describe two scheduling mechanisms used by JavaScript runtimes to execute deferred work. In browser specifications, a macrotask is usually called a task. Timer callbacks, user-input event handlers, and many host notifications run as tasks; promise reactions, queueMicrotask callbacks, and MutationObserver delivery callbacks run as microtasks. This distinction matters because execution order depends on scheduling rules, not simply on which callback was registered first. The event loop coordinates these callbacks; it does not make JavaScript callbacks execute in parallel on the same thread.

In a simplified browser model, the event loop selects one runnable task and executes its JavaScript. Once that work completes and the JavaScript execution stack is empty, the browser performs a microtask checkpoint. It processes queued microtasks in order until none remain, including microtasks added by other microtasks during that checkpoint. The browser may then update rendering before selecting another task, although rendering is not guaranteed after every task. Microtask checkpoints also occur at other specification-defined boundaries, so 'after each task' is a useful interview model rather than the complete browser algorithm.

For example, setTimeout(() => console.log('macro')); Promise.resolve().then(() => console.log('micro')); prints 'micro' before 'macro' when both statements execute in the same script. The script registers the timer and queues a reaction for the already-fulfilled promise. After the current synchronous execution finishes, the promise reaction runs during the microtask checkpoint; the timer callback can run only as a later task. A zero-delay timer does not mean immediate execution: its delay is a minimum scheduling threshold, and other work or browser throttling can postpone it. A Promise constructor's executor, unlike its then callbacks, runs synchronously.

Interviewers usually test whether you can trace synchronous work, then microtasks, then subsequent tasks, while explaining important limits. Recursive microtask scheduling can starve timers and delay rendering because the queue must finish draining. Browsers also have multiple task queues, so unrelated task sources do not share one universal FIFO order. Node.js requires additional care: its event loop has phases for timers and I/O, and process.nextTick uses a separate queue with special priority rules.

Key points to remember

  • Synchronous JavaScript finishes before queued microtasks run.
  • Promise reactions, queueMicrotask callbacks, and MutationObserver deliveries are microtasks.
  • A microtask checkpoint drains the queue, including newly added microtasks.
  • Timers and UI-event dispatch use tasks; browser rendering may occur between tasks.
  • Browser task sources and Node.js event-loop phases prevent a universal FIFO model for all deferred work.

Common mistakes candidates make

  • Treating setTimeout(fn, 0) as immediate execution or assuming it precedes a promise reaction registered later.
  • Calling all promise-related code asynchronous; the Promise constructor's executor runs synchronously.
  • Assuming microtasks yield to rendering or timers after each callback, even when more microtasks remain.

Likely follow-up questions

What happens when a microtask queues another microtask?

The new microtask is added to the queue and runs during the same checkpoint after microtasks already queued. Repeating this indefinitely can prevent subsequent tasks and rendering from progressing.

How does queueMicrotask differ from Promise.resolve().then()?

Both schedule a callback as a microtask, but queueMicrotask does not create a promise chain. An exception in its callback is reported as an uncaught exception, while an exception in a then callback rejects the promise returned by then.

Does await schedule a microtask?

An async function executes synchronously until it reaches await, which suspends that function. Its continuation is scheduled through promise machinery as a microtask once the awaited value is resolved, even when awaiting an already-fulfilled promise or a plain value.

Interview tip: Explain synchronous execution, the microtask checkpoint, and the next task in that order, then trace the example and mention starvation.

Related Interview Questions

1
JavaScriptIntermediate#async#promises

What is a Promise and what are its states?

Open
2
JavaScriptAdvanced#async#promises

Difference between Promise.all, allSettled, race and any?

Open
3
JavaScriptIntermediate#async

How does async/await work?

Open