# Task Queue vs Microtask Queue in JavaScript: Execution Priority Explained

> Understand the difference between JavaScripts Task Queue and Microtask Queue. Discover how microtasks have higher priority for efficient asynchronous code execution.

- Repository: [Leonardo Maldonado/33-js-concepts](https://github.com/leonardomso/33-js-concepts)
- Tags: deep-dive
- Published: 2026-03-03

---

**TLDR:** The microtask queue has higher priority than the task (macrotask) queue, meaning Promise callbacks always execute before setTimeout callbacks, even if the timer was scheduled first.

JavaScript's single‑threaded nature relies on an event loop to manage asynchronous operations. In the leonardomso/33-js-concepts repository, the event loop implementation is dissected to reveal how two distinct queues—the **Task Queue (macrotask)** and the **Microtask Queue**—determine the exact order of callback execution.

## What Is the Task Queue (Macrotask)?

The **Task Queue**, also called the **macrotask queue**, holds callbacks from asynchronous Web APIs that can be deferred until the next event loop tick. According to the source code analysis in `docs/concepts/event-loop.mdx`, this queue contains lower‑priority work such as `setTimeout`, `setInterval`, I/O operations, UI events, and `setImmediate` (Node.js).

When the call stack empties, the event loop picks exactly **one** task from this queue, pushes it onto the stack for execution, and then checks the microtask queue again.

## What Is the Microtask Queue?

The **Microtask Queue** holds high‑priority callbacks that must execute **immediately after** the current synchronous script completes. As documented in the repository's test suite at [`tests/functions-execution/event-loop/event-loop.test.js`](https://github.com/leonardomso/33-js-concepts/blob/main/tests/functions-execution/event-loop/event-loop.test.js), this queue includes:

- `Promise.then`, `.catch`, and `.finally` handlers
- `queueMicrotask()` callbacks
- `MutationObserver` notifications
- Continuations after `await` expressions

Unlike the task queue, the event loop **drains the entire microtask queue** before touching any macrotask. This means if a microtask schedules another microtask, that new callback also runs before any pending macrotask.

## How the Event Loop Processes Both Queues

The event loop follows a strict three‑step algorithm as implemented in JavaScript engines:

1. Execute all synchronous statements on the call stack until it is empty.
2. Drain the **entire microtask queue**, executing every pending microtask and any new microtasks they enqueue.
3. Process **one macrotask** from the task queue, then return to step 1.

This behavior is verified in [`tests/functions-execution/event-loop/event-loop.test.js`](https://github.com/leonardomso/33-js-concepts/blob/main/tests/functions-execution/event-loop/event-loop.test.js) by the test case **"Promises BEFORE setTimeout (microtasks before macrotasks)"** at lines 89‑112, which confirms that promise resolution always precedes timer callbacks regardless of scheduling order.

## Code Examples from the 33-js-concepts Repository

### Promise vs setTimeout Ordering

```javascript
console.log('A');                 // sync
setTimeout(() => console.log('B'), 0); // macrotask
Promise.resolve().then(() => console.log('C')); // microtask
console.log('D');                 // sync
// Output: A, D, C, B

```

Even though `setTimeout` is scheduled before the Promise, the microtask queue drains first. The test **"Promises BEFORE setTimeout"** in [`event-loop.test.js`](https://github.com/leonardomso/33-js-concepts/blob/main/event-loop.test.js) (lines 89‑112) validates this exact output sequence.

### Nested Microtasks Drain Before Any Macrotask

```javascript
console.log('Start');

Promise.resolve()
  .then(() => {
    console.log('First microtask');
    Promise.resolve().then(() => console.log('Second microtask'));
  });

setTimeout(() => console.log('Task'), 0);
console.log('End');

// Output: Start, End, First microtask, Second microtask, Task

```

The second microtask, created inside the first, still executes before the `setTimeout` callback. This demonstrates the **"drain entire microtask queue before any macrotask"** behavior tested at lines 14‑40 in [`tests/functions-execution/event-loop/event-loop.test.js`](https://github.com/leonardomso/33-js-concepts/blob/main/tests/functions-execution/event-loop/event-loop.test.js).

### queueMicrotask vs Promise.then

```javascript
Promise.resolve().then(() => console.log('promise 1'));
queueMicrotask(() => console.log('queueMicrotask 1'));
Promise.resolve().then(() => console.log('promise 2'));
queueMicrotask(() => console.log('queueMicrotask 2'));
/* Output:
   promise 1
   queueMicrotask 1
   promise 2
   queueMicrotask 2
*/

```

Both APIs enqueue callbacks to the same microtask queue, preserving FIFO order. The repository's test **"interleave Promise.resolve() and queueMicrotask in order"** at lines 77‑89 confirms this equivalence.

### Microtask Starvation (Anti-pattern)

```javascript
function endlessMicrotasks() {
  Promise.resolve().then(() => {
    console.log('microtask');
    endlessMicrotasks(); // schedules another microtask immediately
  });
}

setTimeout(() => console.log('timeout'), 0);
endlessMicrotasks(); // ⚠️ never lets the timeout run

```

Because the microtask queue never empties, the macrotask (timeout) never executes, potentially freezing the UI. The documentation in `docs/concepts/event-loop.mdx` (lines 95‑102) explicitly warns against this **microtask starvation** scenario.

## Why JavaScript Maintains Two Separate Queues

The dual‑queue architecture serves distinct execution requirements:

- **Microtasks** handle operations requiring immediate consistency, such as promise resolution and DOM mutations, ensuring state is settled before the next frame renders.
- **Macrotasks** handle deferrable work like timers and I/O that can safely wait until the next event loop iteration.

As noted in `docs/concepts/event-loop.mdx`, this separation prevents long‑running synchronous blocks while ensuring critical asynchronous updates (like promise resolutions) occur as soon as possible without waiting for slower macrotasks.

## Summary

- The **microtask queue** has higher priority than the **task (macrotask) queue**.
- The event loop drains the **entire microtask queue** before processing a single macrotask.
- **Promises**, `queueMicrotask`, and `await` continuations enqueue microtasks.
- **setTimeout**, `setInterval`, and I/O callbacks enqueue macrotasks.
- Recursive microtask scheduling can cause **starvation**, blocking macrotasks indefinitely.

## Frequently Asked Questions

### Do microtasks always run before macrotasks?

Yes. According to the test suite in [`tests/functions-execution/event-loop/event-loop.test.js`](https://github.com/leonardomso/33-js-concepts/blob/main/tests/functions-execution/event-loop/event-loop.test.js), the event loop always empties the microtask queue completely before executing the next macrotask. This means a `Promise.then()` callback will always execute before a `setTimeout(fn, 0)` callback, regardless of which was scheduled first.

### Can microtasks block the browser?

Yes. If you recursively schedule microtasks without yielding, such as calling `Promise.resolve().then()` inside itself indefinitely, the microtask queue never empties. This prevents the event loop from reaching the task queue or rendering frames, causing UI freezing—a scenario called **microtask starvation** documented in `docs/concepts/event-loop.mdx`.

### Is queueMicrotask identical to Promise.resolve().then()?

Functionally, both enqueue callbacks into the same microtask queue with identical priority. The repository's test **"interleave Promise.resolve() and queueMicrotask in order"** confirms they execute in FIFO order relative to each other. However, `queueMicrotask` is more explicit and avoids creating unnecessary Promise objects.

### Where does the await keyword fit in this system?

The `await` keyword pauses async function execution and schedules the continuation as a microtask when the awaited Promise settles. This places `await` resumptions in the same high‑priority queue as `Promise.then()` callbacks, ensuring they execute before any pending macrotasks like timers or I/O events.