📌 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:

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:

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

📝 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.