Skip to main content
  1. My Blog Posts and Stories/

JavaScript: The Event Loop and Micro/Macro Task Queues

··1001 words·5 mins

Introduction #

JavaScript is a single-threaded language, which means it can only execute one task at a time. However, JavaScript has a mechanism called the event loop that allows it to handle asynchronous operations.

In this article, we will discuss how the event loop works and how JavaScript manages the execution of asynchronous tasks using the micro task and macro task queues.

The Event Loop #

The event loop is a mechanism that allows JavaScript to execute async tasks in a non-blocking manner on a single thread.

It is implemented in the JavaScript runtime environment (e.g., the browser or Node.js) and is responsible for managing the execution of tasks in the call stack and the task queues.

The 2 Queues #

The event loop consists of 2 queues:

  1. The Micro task queue
  2. The Macro task queue (Or Task Queue)

The Micro Task Queue #

This is the higher priority queue and is used to schedule tasks that need to be executed after the current task has completed.

The following are examples are scheduled in the micro task queue:

  1. process.nextTick functions
  2. Promise resolve/reject callbacks
  3. queueMicrotask functions
  4. MutationObserver callbacks

The Macro Task Queue #

This is the lower priority queue and is used to schedule tasks that need to be executed after the current task has completed.

The following examples are scheduled in the macro task queue:

  1. setTimeout function calls
  2. setInterval function calls
  3. setImmediate function calls
  4. requestAnimationFrame function calls
  5. I/O operations
  6. UI rendering
To find out more, you can take a look at the HTML Event Loop Spec which refers to the macro task queue as the “task queue”.

How the queues are processed #

sequenceDiagram participant JavaScriptRuntime participant CallStack participant MicroTaskQueue participant MacroTaskQueue activate JavaScriptRuntime loop CallStack Not empty JavaScriptRuntime->>+CallStack: Check if call stack is empty CallStack->>-JavaScriptRuntime: Current function on call stack JavaScriptRuntime->>JavaScriptRuntime: Execute code on call stack end loop MicroTaskQueue Not empty JavaScriptRuntime->>+MicroTaskQueue: Check if Micro Task Queue is empty MicroTaskQueue->>-JavaScriptRuntime: Micro Task to execute JavaScriptRuntime->>JavaScriptRuntime: Execute Micro Task end loop MacroTaskQueue Not empty JavaScriptRuntime->>+MacroTaskQueue: Check if Macro Task Queue is empty MacroTaskQueue->>-JavaScriptRuntime: Macro Task to execute JavaScriptRuntime->>JavaScriptRuntime: Execute Macro Task loop MicroTaskQueue Not empty JavaScriptRuntime->>+MicroTaskQueue: Check if Micro Task Queue is empty MicroTaskQueue->>-JavaScriptRuntime: Micro Task to execute JavaScriptRuntime->>JavaScriptRuntime: Execute Micro Task end end deactivate JavaScriptRuntime

If the call stack is not empty, the javascript runtime will continue to execute the code on the call stack.

When the call stack is empty, the event loop will check the micro task queue first.

If there are tasks in the micro task queue, the event loop will execute them all before checking the macro task queue.

After all the tasks in the micro task queue are executed, the event loop will check the macro task queue.

After the 1st macro task is executed, the event loop will check the micro task queue again before executing the next macro task.

An Example #

Let us start off with an example. Consider the following code snippet:

function recursion(n) {
  if (n === 0) {
    return;
  }
  alert(n);
  recursion(n - 1);
}
setTimeout(() => alert("Timeout"), 0);
Promise.resolve().then(() => alert("Promise"));
recursion(2);
alert("End");
Run

If you click the “Run” button, you will see three alert boxes displayed in the following order:

  1. 2
  2. 1
  3. End
  4. Promise
  5. Timeout

Let us walk through the code step by step

Line 1: setTimeout(() => alert("Timeout"), 0); #

setTimeout schedules a callback function to be executed after a specified delay. How it works is that the callback function is added to the macro task queue, and the event loop will check the macro task queue for tasks to execute when it has nothing to execute in the call stack.

Event Loop
Event Loop after line 1

As we can see in this diagram, the setTimeout scheduled for 0 milliseconds is added to the macro task queue.

However, as there is more code to execute, the event loop will continue to execute the next line of code first.

Line 2: Promise.resolve().then(() => alert("Promise")); #

Promise.resolve() returns a promise that is immediately resolved. The then method is called on the promise, which schedules the callback function to be executed in the micro task queue.

Event Loop
Event Loop after line 2

As we can see in this diagram, the then method is called on the promise, which schedules the callback function to be executed in the micro task queue.

However, as there is more code to execute, the event loop will continue to execute the next line of code first.

Line 3: recursion(2); #

The recursion function is called with the argument 2.

It is a recursive function that alerts the value of n and then calls itself with n - 1 until n is equal to 0.

This function will all more functions to the call stack, which will be executed immediately.

Line 4: alert("End"); #

As the the last line of code is just an alert, it will be executed immediately.

After Line 4 #

After line 3 is executed, the javascript function exits. In this moment, the event loop will be checked for tasks to execute.

The micro task queue is checked first, and the callback function scheduled by the promise is executed.

This results in the alert box “Promise” being displayed.

After the micro task queue is empty, the event loop will check the macro task queue for tasks to execute.

The callback function scheduled by the setTimeout is executed, resulting in the alert box “Timeout” being displayed.

Summary #

You can play around in this segment below to see how the event loop works. In this example, the alert is replaced with console.log instead.

If the code is too small, you can click here to open it in a new tab

Conclusion #

Overall, the event loop is a mechanism that allows JavaScript to handle asynchronous tasks in a non-blocking manner on a single thread.

When programming in JavaScript, it is important to understand how the event loop works and how it manages the execution of tasks using the micro task and macro task queues.

  1. HTML Spec: Event Loop Processing Model
  2. Javascript Info: Event Loop