← Back to list

Understanding Event Loop with Microtasks and Macrotasks in JavaScript

If you’ve ever seen JavaScript print values in an unexpected order, you’ve probably run into the event loop.

Nikhilbaxi · 2026-04-19 08:04 · 4 claps · 3.0 min read
#javascript-development #event-loop #micro-tasks #mutationobserver #messagechannel
Open on Medium ↗
Wiki topics: 🌐 · Web Development

Understanding Event Loop with Microtasks and Macrotasks in JavaScript

If you’ve ever seen JavaScript print values in an unexpected order, you’ve probably run into the event loop.

A common example looks like this:

console.log("start");

setTimeout(() => {
  console.log("timeout");
}, 0);

Promise.resolve().then(() => {
  console.log("promise");
});

console.log("end");

Many developers expect this output:

start
timeout
promise
end

But the actual output is:

start
end
promise
timeout

Why?

The answer lies in JavaScript’s event loop and the difference between microtasks and macrotasks.

JavaScript Is Single-Threaded

JavaScript executes one thing at a time.

That means if one piece of code is running, another piece has to wait.

However, JavaScript can still handle asynchronous operations like timers, API calls, and promises because it uses queues.

The event loop decides which queue to process next.

What Are Macrotasks?

Macrotasks are regular asynchronous tasks.

These tasks are placed into the macrotask queue and are executed only after:

  1. The current synchronous code is complete
  2. All pending microtasks are complete

Common examples of macrotasks include:

  • setTimeout
  • setInterval
  • DOM events like clicks
  • setImmediate in some environments
  • MessageChannel callbacks

Example:

setTimeout(() => {
  console.log("macrotask");
}, 0);

Even with a delay of 0, the callback does not run immediately.

It still waits for the current code and microtasks to finish.

What Are Microtasks?

Microtasks are smaller, higher-priority tasks.

They are executed immediately after the current synchronous code finishes, before the event loop picks the next macrotask.

Common examples of microtasks include:

  • Promise.then()
  • Promise.catch()
  • Promise.finally()
  • queueMicrotask()
  • MutationObserver

Example:

Promise.resolve().then(() => {
  console.log("microtask");
});

Because promises use the microtask queue, they run before setTimeout callbacks.

Understanding the Execution Order

Let’s revisit the original example:

console.log("start");

setTimeout(() => {
  console.log("timeout");
}, 0);

Promise.resolve().then(() => {
  console.log("promise");
});

console.log("end");

Execution happens in this order:

  1. console.log("start") runs
  2. setTimeout callback is added to the macrotask queue
  3. Promise callback is added to the microtask queue
  4. console.log("end") runs
  5. Synchronous code finishes
  6. All microtasks run
  7. Then the first macrotask runs

That is why the output becomes:

start
end
promise
timeout

Easy Rule to Remember

A simple rule is:

Synchronous code first, then microtasks, then macrotasks.

Why This Matters

Understanding microtasks and macrotasks helps you:

  • Debug asynchronous code faster
  • Avoid confusing execution order bugs
  • Perform better in interviews
  • Understand how promises really work
  • Write more predictable JavaScript

This concept becomes even more important when working with frameworks like Angular, React, or Node.js, where promises, timers, and async behavior appear everywhere.

What Is the Event Loop?

The event loop is the mechanism in JavaScript that decides what code should run next.

JavaScript is single-threaded, which means it can only execute one thing at a time. The event loop makes asynchronous behavior possible by continuously checking whether the call stack is empty and whether there are pending tasks waiting in queues.

The process generally looks like this:

  1. Run all synchronous code
  2. Run all pending microtasks
  3. Run the next macrotask
  4. Repeat

A simple mental model is:

Call Stack → Microtask Queue → Macrotask Queue

The event loop keeps repeating this cycle for as long as the application is running.

What Is MessageChannel?

MessageChannel is a browser API that creates two connected ports that can send messages to each other.

Callbacks triggered by MessageChannel are treated as macrotasks, similar to setTimeout.

Example:

const channel = new MessageChannel();

channel.port1.onmessage = () => {
  console.log("MessageChannel callback");
};

console.log("start");

channel.port2.postMessage("hello");

console.log("end");

Output:

start
end
MessageChannel callback

Why?

  1. start runs immediately
  2. postMessage() schedules a macrotask
  3. end runs immediately
  4. Then the event loop processes the onmessage callback

MessageChannel is often used internally by libraries and frameworks because it can schedule tasks faster and more consistently than setTimeout(..., 0).

For example, older versions of React used MessageChannel internally for scheduling work.

What Is MutationObserver?

MutationObserver is a browser API that watches for changes in the DOM.

It can detect things like:

  • Elements being added or removed
  • Attribute changes
  • Text changes inside elements

Its callback is placed in the microtask queue, so it runs before macrotasks like setTimeout.

Example:

const div = document.createElement("div");

const observer = new MutationObserver(() => {
  console.log("DOM changed");
});

observer.observe(div, {
  attributes: true
});

div.setAttribute("class", "active");

console.log("end");

Output:

end
DOM changed

Why?

  1. setAttribute() changes the DOM immediately
  2. The MutationObserver callback is scheduled as a microtask
  3. end logs first because it is still part of the current synchronous code
  4. After synchronous code finishes, the microtask queue runs

MutationObserver is commonly used for:

  • Watching dynamic UI updates
  • Detecting when elements are inserted into the page
  • Integrating with third-party scripts
  • Building browser extensions
  • Tracking changes in large applications like Angular or React

Final Thoughts

The event loop can feel confusing at first, but once you understand the queue priority, the behavior becomes much easier to predict.

Remember:

  • Synchronous code runs first
  • Then microtasks run
  • Then macrotasks run

Once you understand that rule, most JavaScript async questions become much easier to solve.


메타데이터
post_id
fbb5cc8ee6d8
slug
understanding-event-loop-with-microtasks-and-macrotasks-in-javascript-fbb5cc8ee6d8
url
https://medium.com/@nikhilbaxi3/understanding-event-loop-with-microtasks-and-macrotasks-in-javascript-fbb5cc8ee6d8
canonical_url
https://medium.com/@nikhilbaxi3/understanding-event-loop-with-microtasks-and-macrotasks-in-javascript-fbb5cc8ee6d8
author_url
https://medium.com/@nikhilbaxi3
status
ok
fetched_at
2026-09-05 20:55:03