# The Node.js Event Loop Explained

In this article we are gonna study one of the most important concepts in Node.js which is the Event Loop. Now many of us initially hear this line **JavaScript is single threaded** and instantly get confused. Because at the exact same time they also see that many javascript programs handle multiple things at same time. So naturally one question appears:

> "If JavaScript is single threaded, then how is all this asynchronous behavior even possible?"
> 
> "And if this is possible, is JavaScript single threaded?"

And that is exactly what the event loop helps solve. But before understanding the event loop itself, we first need to understand what Node.js actually is.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Quick Note: I have made a whole video on this which you can check out: <a target="_blank" rel="noopener noreferrer nofollow" class="text-primary underline underline-offset-2 hover:text-primary/80 cursor-pointer" href="" style="pointer-events: none;">https://www.youtube.com/watch?v=IjnHypxGExc</a></div>
</div>

## What the event loop is

Now many initially think the event loop is some magical hidden engine. But honestly the easiest way to think about it is:

> "A task manager continuously checking completed async work."

That is the simplest correct mental model. Conceptually it behaves somewhat like:

```javascript
while(true) {
    check completed tasks
    execute callbacks
}
```

Obviously internally Node.js is much more complex than this. But initially this mental model is enough. The event loop continuously checks:

```plaintext
Did some async work complete?
```

If yes:

```plaintext
Execute its callback
```

If not:

```plaintext
Continue checking other things
```

And this loop keeps running until nothing is left pending. But now the bigger question becomes:

> "Why was this even needed in the first place?"

## Why Node.js needs an event loop

To understand this, we first need to understand one very important thing: **JavaScript is single threaded.** Which means:

```plaintext
One main thread
        |
One thing executes at a time
```

Now suppose Node.js did NOT have an event loop. And suppose we did this:

```javascript
readHugeFile();
```

Now imagine this file takes 5 seconds to read. If JavaScript directly waited for the file. Everything freezes, No API requests. No timers. No other users. No callbacks. Entire server blocked. Which honestly would be terrible. Because servers constantly handle Multiple users , Database queries , File systems , Authentication , APIs ,Background tasks... And much more (Yes! all true) So Node.js needed a system where slow tasks could happen separately while JavaScript continued running normally. And this is exactly where the event loop enters.

## Understanding JavaScript vs Node.js

Now another important confusion many have initially is **JavaScript != Node.js.** JavaScript itself is just a programming language. It gives you things like:

*   Variables
    
*   Functions
    
*   Loops
    
*   Conditionals...
    

But things like:

```javascript
setTimeout()
fetch()
fs.readFile()
```

are NOT originally part of JavaScript itself. These are provided by the environment. In browsers Browser provides APIs. In Node.js: Node.js provides APIs. And internally Node.js uses:

1.  V8 Engine
    
2.  C++ bindings
    
3.  libuv
    

Now among these, libuv is extremely important for asynchronous behavior. Because libuv provides things like:

1.  Event loop
    
2.  Thread pool
    
3.  Async I/O handling
    

And this is what allows Node.js to behave asynchronously while JavaScript itself still remains single threaded.

## Task queue vs call stack (conceptual only)

Now before going deeper into the event loop, we first need to understand two very important concepts.

## Call Stack

The call stack is where JavaScript executes functions. Suppose we do:

```javascript
function mine() {
    console.log("Mining Diamonds");
}

mine();
```

Roughly the flow becomes:

![](https://cdn.hashnode.com/uploads/covers/69515b4d02adfd4e80d3c2b4/4d4fd461-5fc1-4674-b513-0c238bcbd5ec.png align="center")

Functions keep entering the stack. Then after execution they leave the stack. This is where actual JavaScript execution happens.

## Task Queue

Now another thing exists called the task queue. This queue stores completed async callbacks waiting to execute. For example:

```javascript
setTimeout(() => {
    console.log("Creepr Spawned! Oh Mannn");
}, 0);
```

Now many initially think:

```plaintext
0ms = instant execution
```

But that is NOT what happens. Suppose we do:

```javascript
console.log("Start");

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

console.log("End");
```

Output:

```plaintext
Start
End
Timer
```

Now why did this happen? Because even though the timer completed quickly, its callback still waits inside the task queue. The event loop only moves it into the call stack once the stack becomes empty. Conceptually:

```plaintext
CALL STACK            TASK QUEUE

console.log()         setTimeout callback
console.log()
```

Once the stack becomes empty:

```plaintext
Event Loop
     |
Moves callback into stack
```

Then the callback finally executes. And this mental model is extremely important.

## How async operations are handled

Now this is where things start becoming interesting. Suppose we do:

```javascript
const fs = require("fs");

fs.readFile("diamond.txt", "utf-8", (err, data) => {
    console.log("File Read Complete");
});

console.log("Mining...");
```

Output:

```plaintext
Mining...
File Read Complete
```

Now many at strt think JavaScript itself is reading the file. But that is NOT what happens. What actually happens is roughly this:

```plaintext
JavaScript
     |
Registers async task
     |
Node.js/libuv handles work
     |
Callback returns later
```

And while the file is being read:

```javascript
console.log("Mining...");
```

can execute immediately. This is one of the biggest reasons Node.js feels non-blocking. Because the main JavaScript thread is NOT sitting idle waiting for the file. Instead:

1.  Task gets delegated
    
2.  Background system handles it
    
3.  Event loop keeps checking completion
    
4.  Callback enters queue once ready
    
5.  Callback finally executes
    

This architecture is extremely important.

## Event loop execution flow

Now let us understand the overall execution flow more clearly. Suppose this is our code:

```javascript
console.log("Hello");

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

console.log("End");
```

Although if we read this line by line and think, this is the output we expect:

```plaintext
Hello
Timer Callback
End
```

But Node.js does NOT execute async callbacks immediately. The actual flow is closer to this:

### Step 1 — Environment initializes

Node.js first initializes its environment internally. Things like Global objects, Runtime setup, Internal variable, APIs; all get prepared.

### Step 2 — Top level code executes

Then synchronous top level code starts executing. So first:

```javascript
console.log("Hello")
```

runs immediately. Output:

```plaintext
Hello
```

### Step 3 — Async callbacks register

Now this line:

```javascript
setTimeout(...)
```

does NOT execute its callback immediately. Instead:

```plaintext
Timer gets registered
```

Meaning Node.js now knows:

```plaintext
"Run this callback later"
```

### Step 4 — Remaining synchronous code executes

Then:

```javascript
console.log("End")
```

executes. Output becomes:

```plaintext
Hello
End
```

### Step 5 — Event loop checks completed tasks

Now the call stack becomes empty. The event loop checks: Did timer complete? If yes: Move callback into queue. Then: Push callback into call stack. Then finally: Timer Callback gets printed. So overall output becomes:

```plaintext
Hello
End
Timer Callback
```

## Timers vs I/O callbacks (high level)

Now another interesting thing appears with timers and file operations. Suppose we do:

```javascript
setTimeout(() => {
    console.log("Timer Finished");
}, 0);

fs.readFile("diamond.txt", () => {
    console.log("File Finished");
});
```

Now many try predicting which one executes first. But internally Node.js handles these inside different event loop phases. At a very high level:

```plaintext
Timers
   |
I/O callbacks
   |
Other checks
```

Now we are intentionally avoiding very deep phase internals in this article. But this is enough to understand one important thing:

```javascript
setTimeout(fn, 0)
```

does NOT mean:

```plaintext
Execute instantly
```

It mainly means:

```plaintext
Execute AFTER minimum delay
```

And only once:

1.  The delay completed
    
2.  Stack became empty
    
3.  Event loop reached correct phase
    

can the callback actually execute. This distinction is extremely important.

## Understanding the queue analogy

One of the easiest ways to visualize the event loop is using a queue analogy. Suppose callbacks are standing in a line. Like this:

```plaintext
Timer Callback
File Callback
Database Callback
```

Now the event loop behaves somewhat like:

```plaintext
"Okay who completed first?"
```

Whoever completed first enters the queue first. And because queues work on:

```plaintext
FIFO
First In First Out
```

callbacks generally execute in queue order. Which is why something like:

```javascript
setTimeout(() => {
    console.log("First");
}, 0);

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

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

usually gives:

```plaintext
First
Second
Third
```

because callbacks entered the queue in that order.

## Role of event loop in scalability

Now honestly this is one of the biggest reasons Node.js became extremely popular. Imagine two servers.

### Blocking Server

```plaintext
User 1 Request
      |
Wait for DB
      |
Everything blocked
```

Now every other user waits. Terrible scalability.

### Event Loop Based Server

```plaintext
User 1 DB Request
        |
Delegate async task
        |
Continue serving others
```

Now while one user waits for:

1.  Database
    
2.  File system
    
3.  API response
    
4.  Authentication
    

Node.js can continue serving completely different users. And this is one of the core reasons Node.js performs extremely well for:

1.  APIs
    
2.  Realtime systems
    
3.  Chat applications
    
4.  Streaming
    
5.  Web servers
    
6.  Socket based applications
    

The event loop is one of the biggest reasons behind this scalability.

## Event loop execution cycle visualization

![](https://cdn.hashnode.com/uploads/covers/69515b4d02adfd4e80d3c2b4/e3c0e95b-d859-4adf-b042-7a4577ec7128.png align="center")

## Call stack + task queue + event loop flow

![](https://cdn.hashnode.com/uploads/covers/69515b4d02adfd4e80d3c2b4/e9e40624-9df8-40e0-ac12-fdf4776cdf52.png align="center")

## Conclusion

With this we now understand what the Node.js event loop actually is and why it became necessary for asynchronous JavaScript execution. We saw how JavaScript still remains single threaded while Node.js handles asynchronous operations efficiently, how the call stack and task queue work together, how async operations get delegated, how timers differ from I/O callbacks, and why the event loop plays such a huge role in Node.js scalability.

Although the event loop may initially feel magical, internally it mainly behaves like a task manager continuously checking completed asynchronous work and scheduling callbacks for execution.

I hope you enjoyed it!

Thank You.
