🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
JS MASTER CLASS /// MASTER THE ENGINE /// BUILD LOGIC /// ASYNC PATTERNS /// JS MASTER CLASS /// MASTER THE ENGINE ///

The Event Loop: Microtask/Macrotask Ordering | JavaScript Tutorial - In-Depth Guide

Go deeper on the event loop: the distinction between the microtask queue and the macrotask (task) queue, why Promises jump ahead of setTimeout, and tracing exact execution order for mixed sync/async code.

Total XP: 0|💻 javascript XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core logic.

Quick Quiz //

Between two macrotasks, does the event loop run just one pending microtask, or the entire microtask queue?


🚀 LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
🎓 COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

Beyond the basic Call Stack / Web APIs / Callback Queue model, professional JavaScript work requires precisely predicting execution order — and that means understanding the distinct microtask and macrotask queues the event loop actually processes.

1The Event Loop: Microtask/Macrotask Ordering | JavaScript Tutorial - In-Depth Guide Part 1

The event loop doesn't have just one queue — it has (at least) two: the microtask queue and the macrotask (a.k.a. 'task') queue, and they are NOT processed with equal priority.

+
// Two distinct queues:
// 1. Microtask queue: Promise callbacks, queueMicrotask()
// 2. Macrotask queue: setTimeout, setInterval, I/O, UI events
localhost:3000
🔄

Two Distinct Queues

2The Event Loop: Microtask/Macrotask Ordering | JavaScript Tutorial - In-Depth Guide Part 2

After each single macrotask finishes, the event loop fully drains the ENTIRE microtask queue before running the next macrotask — even if new microtasks are added while draining.

+
console.log('1: sync');
setTimeout(() => console.log('4: macrotask'), 0);
Promise.resolve().then(() => console.log('3: microtask'));
console.log('2: sync');
// Output order: 1, 2, 3, 4
localhost:3000

Microtasks Fully Drain First

3The Event Loop: Microtask/Macrotask Ordering | JavaScript Tutorial - In-Depth Guide Part 3

This explains a classic gotcha: setTimeout(fn, 0) does NOT run immediately after the current synchronous code — it waits for both the rest of the synchronous code AND the entire microtask queue to finish first.

+
setTimeout(() => console.log('macrotask'), 0);
Promise.resolve().then(() => console.log('microtask'));
// 'microtask' logs first, despite setTimeout appearing earlier in the code
localhost:3000

0ms setTimeout Isn't Instant

4The Event Loop: Microtask/Macrotask Ordering | JavaScript Tutorial - In-Depth Guide Part 4

async/await is built on Promises under the hood, so code after an 'await' resumes as a microtask — this is why interleaving async functions with setTimeout can produce surprising, non-obvious ordering.

+
async function example() {
  console.log('1');
  await null; // resumes as a microtask
  console.log('3');
}
example();
console.log('2');
// Output: 1, 2, 3
localhost:3000

await Resumes as a Microtask

5The Event Loop: Microtask/Macrotask Ordering | JavaScript Tutorial - In-Depth Guide Part 5

Tracing execution order for mixed sync/microtask/macrotask code is a genuinely valuable debugging and interview skill — practice by mentally running the sync code first, then draining all microtasks, then processing one macrotask at a time.

+
console.log('A');
setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => {
  console.log('C');
  Promise.resolve().then(() => console.log('D'));
});
console.log('E');
// Order: A, E, C, D, B
localhost:3000

Tracing Execution Order

6Step-by-Step Breakdown

The event loop doesn't have just one queue — it has (at least) two: the microtask queue and the macrotask (a.k.a. 'task') queue, and they are NOT processed with equal priority.

After each single macrotask finishes, the event loop fully drains the ENTIRE microtask queue before running the next macrotask — even if new microtasks are added while draining.

Checkpoint: Between two macrotasks, does the event loop run just one pending microtask, or the entire microtask queue?

  • The entire microtask queue, fully drained
  • Just one microtask at a time

This explains a classic gotcha: setTimeout(fn, 0) does NOT run immediately after the current synchronous code — it waits for both the rest of the synchronous code AND the entire microtask queue to finish first.

Checkpoint: If a setTimeout(fn, 0) and a Promise.resolve().then(fn2) are both scheduled in the same synchronous block, which one runs first?

  • The Promise's .then() callback, since it's a microtask
  • The setTimeout callback, since it's scheduled with 0ms

async/await is built on Promises under the hood, so code after an 'await' resumes as a microtask — this is why interleaving async functions with setTimeout can produce surprising, non-obvious ordering.

Tracing execution order for mixed sync/microtask/macrotask code is a genuinely valuable debugging and interview skill — practice by mentally running the sync code first, then draining all microtasks, then processing one macrotask at a time.

Next, we'll explore 'Microtasks'.

Level Up 🚀

Advanced cheat sheets, SEO tricks, and interview prep for this topic.

Browser Support

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

Fully supported.

Accessibility (A11y)

1Understand Event Loop Ordering When Sequencing Focus Management After Async Updates

When moving focus programmatically after an async data update, knowing whether your focus-setting code runs as a microtask (right after the data resolves) or is deferred to a macrotask (like a setTimeout) affects whether focus lands correctly before or after the DOM has actually updated and is ready to receive it.

SEO Implications

  • 1

    No Direct SEO Effect

    Event loop internals are a JavaScript execution-model concept; SEO relevance is limited to writing correct, non-janky async code that avoids blocking rendering.

Best Practices

Remember Promises Always Win a Race Against setTimeout(fn, 0)

This is a common source of confusion; internalizing that microtasks fully drain before any macrotask runs prevents incorrect assumptions about execution order in mixed async code.

Use queueMicrotask() Directly (Not a 0ms setTimeout) When You Specifically Need Microtask Timing

If your intent is "run this as soon as possible, after current synchronous code, but before any macrotask," queueMicrotask() expresses that intent directly rather than relying on Promise machinery as a workaround.

Frequent Bugs

THE BUG

Assuming `setTimeout(fn, 0)` runs before a Promise's `.then()` callback because it appears earlier in the source code or because the delay is 0, leading to incorrect assumptions about execution order.

THE FIX

Remember that all Promise-based microtasks fully drain before any macrotask (including a 0ms setTimeout) gets to run, regardless of the order they were scheduled in.

THE BUG

Writing an infinite microtask-producing loop (e.g. a .then() that always schedules another .then()), which can starve macrotasks (like rendering or user input) from ever running, since the microtask queue must fully drain first.

THE FIX

Ensure recursive or chained microtask scheduling has a genuine termination condition, or intentionally yield to a macrotask (e.g. via setTimeout) periodically if the work needs to continue indefinitely.

Real-World Examples

Debugging an Unexpected Console Log Order

A developer needed to explain to a teammate why a chain of Promise .then() calls all logged before a setTimeout(fn, 0) that was scheduled earlier in the same function.

setTimeout(() => console.log('timeout'), 0);
Promise.resolve()
  .then(() => console.log('then 1'))
  .then(() => console.log('then 2'));
// Output: then 1, then 2, timeout — the whole microtask chain drains first

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Assuming setTimeout(fn, 0) runs immediately or before pending Promise callbacks

// Promise .then() callbacks always run before a same-tick setTimeout(fn, 0)

The Solution //

Remember the microtask queue always fully drains before the next macrotask runs, regardless of setTimeout delay.

Lesson Glossary

[01]Microtask Queue

A high-priority queue (Promise callbacks, queueMicrotask) fully drained before the next macrotask.

Code Preview
.then() callbacks

[02]Macrotask Queue

The lower-priority queue for setTimeout, setInterval, I/O, and UI events — one processed per event loop tick.

Code Preview
setTimeout callbacks

[03]Draining the Queue

Running every currently-queued microtask, including new ones added during the process, before moving on.

Code Preview
fully empties before next macrotask

[04]await as a Microtask

The fact that code resuming after an await is scheduled onto the microtask queue, like a .then() callback.

Code Preview
await resumes via microtask

[05]Event Loop Tick

One cycle of the event loop: running one macrotask, then fully draining the microtask queue.

Code Preview
one iteration

Continue Learning