ReviseAlgo Logo

Asynchronous JavaScript

Microtasks vs Macrotasks

Master task scheduling priorities in JavaScript. Learn the execution precedence rules between the Microtask Queue and the Macrotask Queue.

Last Updated: July 15, 2026 10 min read

1. Introduction

Asynchronous callbacks are scheduled in two separate queues: the Microtask Queue and the Macrotask Queue (commonly called the Callback Queue). The JavaScript engine processes these queues with different priorities, which affects the order in which code executes.

2. Why It Matters

Understanding queue priorities is critical to predicting the execution order of complex asynchronous code (mixing Promises, timers, and mutation observers) and preventing UI rendering blocks.

3. Real-World Analogy

Think of a Post Office Service counter:

  • Macrotasks (Standard Mail Delivery): Packages arriving at the loading dock. Each package delivery is a separate, standalone task. The clerk processes one package, then takes a break to check on other duties before processing the next.
  • Microtasks (VIP Customers at the Counter): A customer standing directly at the window who keeps adding requests: "Oh, and mail this document, and photocopy this, and stamps please." The clerk must fulfill all of this customer's immediate requests (exhaust the microtask queue) before leaving the counter to process the packages at the loading dock (macrotasks) or clean the lobby (DOM render).

4. The Execution Rules

The Event Loop coordinates the execution of tasks using these priority rules:
1. Execute all synchronous code on the Call Stack first.
2. When the Call Stack is empty, check the Microtask Queue. Execute all microtasks in the queue sequentially until it is completely empty. If a microtask adds another microtask, it is executed immediately in the same cycle.
3. When the Microtask Queue is empty, check if the browser needs to perform a DOM rendering update.
4. Fetch the first task from the Macrotask Queue, push it onto the Call Stack, and execute it.
5. Repeat the loop (go back to Step 2).

5. Task Types

Queue Type Task Sources Priority & Execution Behavior
Microtasks Promise.then/catch/finally, queueMicrotask(), MutationObserver, process.nextTick (Node) High priority. The queue is executed until completely empty before moving on.
Macrotasks setTimeout, setInterval, setImmediate (Node), DOM events, network I/O Low priority. Only one macrotask is executed per event loop iteration.

6. Practical Example

This script demonstrates how promise callbacks (microtasks) execute before timers (macrotasks), even if the timer has a delay of 0ms:

7. Common Mistakes

  • Starving the Macrotask Queue: Adding microtasks recursively inside a microtask callback creates an infinite loop that keeps the Microtask Queue filled. This starves the event loop, preventing macrotasks (like timers) from running and blocking DOM rendering updates.

8. Quick Quiz

Q1: If a promise callback (microtask) schedules another promise callback, when will that new callback execute?

A) In the next event loop iteration, after running pending timers

B) In the current iteration, before any pending timers are executed

Answer: B — The Event Loop executes all pending microtasks in the queue, including any new ones added during the current run, before moving on to macrotasks or rendering.

9. Scenario-Based Challenge

The Priority Sequence Resolver:

Analyze the execution order of a script that schedules: setTimeout, Promise.resolve().then(), queueMicrotask(), and a synchronous console.log. Trace the state of both queues step-by-step to show the exact output sequence.

10. Debugging Exercise

Explain why the DOM text change is never visible, and how to fix it:

function updateUi() {
  const el = document.getElementById('title');
  el.textContent = 'Processing...';

// Objective: Let user see 'Processing...' before starting heavy calculations queueMicrotask(() => { // Heavy synchronous calculation const start = Date.now(); while (Date.now() - start < 1000) {} el.textContent = 'Done!'; }); }

View Solution

Diagnosis: The heavy calculation is scheduled as a microtask. Since microtasks execute immediately after the synchronous code finishes and before DOM rendering updates, the browser does not render the 'Processing...' text update. Instead, the calculation runs and blocks the main thread, and then the text changes directly to 'Done!'.

Fix: Schedule the heavy calculation as a macrotask using setTimeout, which allows the browser to perform a DOM rendering update in between ticks:

function updateUi() {
  const el = document.getElementById('title');
  el.textContent = 'Processing...';

setTimeout(() => { const start = Date.now(); while (Date.now() - start < 1000) {} el.textContent = 'Done!'; }, 0); // Allows DOM render to complete first }

11. Interview Questions

🟢 Q1: Describe the order of execution between microtasks and macrotasks in the Event Loop.

Answer:
1. Execute all synchronous code on the Call Stack.
2. When the Call Stack is empty, execute all microtasks in the Microtask Queue until it is completely empty.
3. Perform DOM rendering updates if needed.
4. Dequeue and execute the first task from the Macrotask Queue.
5. Repeat this loop, checking and emptying the Microtask Queue after every macrotask execution.

12. Production Considerations

  • queueMicrotask for Batching: Use queueMicrotask() when you want to schedule a task to run immediately after the current synchronous block finishes, but before yielding control to browser rendering or timers. This is useful for batching operations to avoid redundant rendering cycles.