JavaScript Event Loop: Breaking Down the Mystery
Tags: javascript, node, softwareengineering, programming
Published: 2023-08-03 16:40:38

What happens when the following code is run in Node.js?

```javascript
setTimeout(() => console.log(1), 10)
Promise.resolve().then(() => console.log(2))
console.log(3)
```

If your answer was different from:
```javascript
3
2
1
```

Perhaps you don't fully understand the execution order of JavaScript and the operation of the Event Loop.

No worries, I'll try to explain.

**First of all**, if you have doubts about what is:
- **JavaScript**
- **ECMAScript**
- **JavaScript _Runtime_**

I recommend that you read the [**glossary**](#glossary) before continuing.

Now, let's go, I will explain what happens at each stage of the execution of this JavaScript code.

## Main Thread
Node interprets the JavaScript file from top to bottom, line by line, in a single thread.

### Running setTimeout()
The _main thread_ will interpret the first instruction, add it to the Call Stack, where it will be executed and removed from the _Call Stack_.

![Visualization of the Main Thread executing the first function call: setTimeout(() => console.log(1), 10)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b8ry8ueju3rhnvuvnwnq.png)

The `setTimeout` instruction is used to schedule the execution of a function after certain milliseconds. 

This function is part of the [`libuv`](https://libuv.org/) library, which Node uses to create a **Timer** without blocking the main _thread_. 

![The main thread executes the setTimeout function, which starts a timer in a new thread, through a library called libuv. At the end of the timer, the callback will be added to the macro-task queue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mrcidhlqrcjp8xk4swtc.png)

After starting the _Timer_, the main _thread_ will remove the instruction from the _Call Stack_.

![Main Thread pops from call stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/itlk0d6813hhs7d4uyum.png)

At the end of the interval, the timer will add the _callback_ of the _setTimeout_ function to the **macro-task** queue.

### Running Promise.resolve().then()
While the _Timer_ of the _libuv_ library waits for the 10ms, the _Main thread_ will interpret the next line of the file.

![Main thread consumes the next instruction from the call stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2yphjc4rv34lmr0y3h84.png)

The instruction this time is
```javascript
Promise.resolve().then(() => console.log(2))
```

The main _thread_ will execute the function [Promise](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise).resolve().then()

> _Promise_ is an object that represents a completion or failure of an asynchronous operation.

By calling the resolve() function without any parameter, we are declaring _Promise_ that does not return any value, but that's okay.


![Executing the function Promise.resolve()](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/usel8ojsgldntndmdw1z.png)

For now, we are more interested in the behavior of the _.then_ function of a _Promise_.

By passing `() => console.log(2)` as callback for our _Promise_, we are telling Node to execute this code as soon as the _Promise_ is successfully finished.

In other words, we are saying that, as soon as the [resolve()](https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) method of the _Promise_ is executed, Node should execute our _console.log(2)_ instruction.

But, that's not exactly how it works.

> Every _Promise_ _callback_ is sent **instantly** to a special queue called **Micro Tasks Queue**.


![Pushes Promise callback to MicroTasks queue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5tx6tt1l0mt6yd31vdqe.png)

### Recapping
This is the current state of the script execution:


![Current state of the script execution](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1l4p3kq8lcvzxshxwgk.png)


Everything that happened so far, surely, took less than 10 milliseconds, which is why the **Timer** has not yet added the instruction of `console.log(1)` to the _Macro Tasks Queue_.

But, by using _libuv_, the _Main thread_ can continue working normally, in a **non-blocking** manner.

Ok, you might be wondering:

### Event Loop
Throughout this process, with each interpretation of a new line from the file, the _Event Loop_ performed a very important, albeit repetitive function.

- Check if the _Call Stack_ was empty.

![Event Loop asking if the Call Stack is empty](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dff0sbaxt7gnbqe2i0ch.png)



As you can see, the answer was always: **NO!**

At no time during the execution of this script was the _Call Stack_ empty, so our friend _Event Loop_ will keep waiting.

### Emptying the _Call Stack_
Now, the _Main Thread_ interprets the last instruction of the file.

![Main Thread consumes the last call from the call stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e7gq0vg8ceov0ni2zmj8.png)

This is a simple instruction, which displays a value on the _console_, its result is:
```bash
3
```

And, for the first time, the _Call Stack_ is empty!

### Event Loop
Now, the most awaited moment for the _Event Loop_, the moment when it has the power to act!

It will only validate the other queues when the _Call Stack_ is empty!

At each loop, it will:
- Process all tasks in the _Micro Tasks_ queue
  - Adding them to the _Call Stack_
- Process 1 task from the _Macro Tasks_ queue
  - Adding it to the _Call Stack_
- Wait for the _Call Stack_ to empty
- Repeat

**The _Main Thread_ executes every instruction in the main context.**

Now, continuing the execution of the example code:

#### Micro Tasks
When the _Call Stack_ becomes empty, it means that the _Main Thread_ is not executing anything.

Then, the _Event Loop_ consumes all tasks from the _Micro Tasks Queue_ and adds them to the _Call Stack_.
![Event Loop consuming function from the micro tasks queue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7lyjf1z0m4ohlbsg6ou7.png)

Next, the _Main Thread_ consumes the instruction from the _Call Stack_ and executes it.
![Main Thread consuming call stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zs02h5a9oejxr7ub701c.png)

```javascript
console.log(2) // Writes 2 to the console
```
Now, the _Call Stack_ becomes empty again.

Then, the _Event Loop_ looks for more tasks in the _Micro Tasks_ queue.

![Current state of the application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ztyrlifqa2zdf8izsqg1.png)

As it is empty, it finishes its work in the _Micro Tasks Queue_ and starts consuming the _Macro Tasks Queue_.

#### Macro Tasks
Now, suppose that the 10-millisecond interval has passed and the _Timer_ has inserted the console.log(1) function into the _Macro Tasks_ queue, the _Event Loop_ will transfer 1 instruction from the _Macro Tasks Queue_ to the _Call Stack_.


![Event Loop consuming Macro Tasks queue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xm31nxba5ktgh0ia781k.png)

Then, the _Main Thread_ consumes the last instruction from the _Call Stack_ and executes it.
![Main Thread consuming the Call Stack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y7sqoyvdrk692lybb9im.png)

```javascript
console.log(1) // Writes 1 to the console
```

**Important point:** If there were still instructions in the Micro Tasks queue, these would be processed. But, as everything is empty, the program execution is heading towards the end.

That's why the code:
```javascript
setTimeout(() => console.log(1), 10)
Promise.resolve().then(() => console.log(2))
console.log(3)
```

Will result in:

```javascript
3
2
1
```

> We've reached the end - Arlindo Cruz

Now you understand what happens behind the scenes of JavaScript. The **Event Loop** manages the queues of _micro_ and _macro_ tasks and, with that, ensures that asynchronous instructions are executed harmoniously in the context of the main thread.

Understanding how it works helps us write more efficient codes and better predict the behavior of our applications.

Next time you're writing JavaScript code, I hope you remember everything that happens behind the scenes of the **Event Loop**.

See you later!

## Glossary
### JavaScript
It's a high-level, dynamic, interpreted programming language that supports multiple programming paradigms (functional, imperative, object-oriented).

It's a "medium" of conversation between something you want to do and what the computer executes. 

### ECMAScript
It's a [set of rules](https://tc39.es/ecma262/2023/) that defines how JavaScript should work, it defines the language standards (syntax, data types, control structures, and operators), and JavaScript is the implementation of these standards.

If you want to understand better, read [this article](https://hcode.com.br/blog/o-que-e-ecmascript-e-o-mesmo-que-javascript)

### JavaScript Runtime
It's the **engine** that executes JavaScript code.

When writing JavaScript code, you write instructions (which follow the rules defined by ECMAScript), but to execute these instructions, you need a _Runtime_.

It's as if JavaScript were a recipe and the _Runtime_ was a cook who executes the recipe.

[Node](https://nodejs.org/), [V8](https://v8.dev/) and [SpiderMonkey](https://firefox-source-docs.mozilla.org/js/index.html) are the most well-known JavaScript _runtimes_ in the world.