JavaScript  

JavaScript Event Loop: Microtasks vs Macrotasks Explained

📌 Introduction

JavaScript is a single-threaded language, meaning it can only do one thing at a time. However, modern applications often need to perform many tasks such as fetching data from APIs, waiting for user actions, or handling timers. If JavaScript could only run one thing at a time, wouldn’t the browser freeze while waiting for these tasks? Luckily, the answer is no, thanks to the event loop. The event loop works with the call stack, Web APIs, and queues to handle tasks efficiently. Understanding how it works helps you write better, non-blocking code.

🔄 The Call Stack

The call stack is like a list that keeps track of the functions JavaScript is currently running. When you call a function, it is added to the stack. When the function finishes, it is removed. This process continues until the stack is empty.

Example:

function first() {
  console.log("First");
}

function second() {
  first();
  console.log("Second");
}

second();

Output:

First
Second

✅ Here, second() goes on the stack first. Inside it, first() is called, so it goes on the stack. When first() finishes, it is removed, then the rest of second() runs. This is how the call stack ensures order.

⏳ The Event Loop

The event loop is like a manager that constantly checks two things:

  1. Is the call stack empty? (Is JavaScript currently doing something?)
  2. Are there any tasks waiting in the task queues?

If the call stack is empty and tasks are waiting, the event loop moves a task from a queue to the stack so it can run. This way, JavaScript can perform asynchronous tasks like network requests without blocking the main program.

🗂️ Macrotasks

Macrotasks are tasks that usually come from timers, events, or I/O operations. They are scheduled to run after the current script finishes. Examples include:

  • setTimeout
  • setInterval
  • I/O tasks (fetching data from a server)
  • UI rendering events

Example:

console.log("Start");

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

console.log("End");

Output:

Start
End
Macrotask

✅ Even though setTimeout has 0 delay, it runs later, after the main code is finished. This shows how macrotasks are handled.

📋 Microtasks

Microtasks are small, high-priority tasks that are run immediately after the current code finishes, and before any macrotasks. They are mainly used for promises and updates that need to happen quickly.

Examples:

  • Promise callbacks (.then, .catch, .finally)
  • MutationObserver

Example:

console.log("Start");

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

console.log("End");

Output:

Start
End
Microtask

✅ Notice that the microtask runs before the macrotask, even though both were scheduled after the main code.

Workflow

🆚 Microtasks vs Macrotasks Example

When both microtasks and macrotasks are used together, microtasks are always executed first.

Example:

console.log("Start");

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

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

console.log("End");

Output:

Start
End
Microtask
Macrotask

✅ Here, the event loop checks the microtask queue first, so the promise callback runs before the macrotask (setTimeout).

📘 Best Practices

  • Use microtasks (Promises) for quick, short tasks that must run right away.
  • Use macrotasks (setTimeout, setInterval) for tasks that can wait until the current code finishes.
  • Avoid heavy tasks on the call stack, as they block everything else.
  • Always keep in mind the execution order to avoid unexpected results.

📝 Summary

The JavaScript event loop is the system that lets JavaScript handle multiple tasks without blocking. It works with the call stack, macrotask queue, and microtask queue. Macrotasks (like setTimeout) are handled after the script finishes, while microtasks (like promises) run right after the current code and before macrotasks. This priority system ensures smooth and efficient execution. By understanding the event loop, microtasks, and macrotasks, developers can write more predictable and efficient asynchronous code.