# The Node.js Event Loop 



Node.js run single thread. But handle thousands requests. How? Event loop. Event loop = manager that decide what code run when. Without it, Node.js freeze on first slow task.

This about understanding event loop and how it make Node.js fast.

* * *

## What The Event Loop Is

Event loop = forever-running manager. Check what task ready, run it, repeat.

### Simple Definition

Event loop = system that manage code execution. When one task finish, event loop grab next task. One at time, but very fast.

### Real Analogy

Coffee shop barista:

```
Barista job = event loop

1. Customer 1 order coffee
   → Add to task queue

2. Customer 2 order coffee
   → Add to task queue

3. Customer 1 order ready
   → Grab from queue, serve customer 1

4. Customer 3 order
   → Add to task queue

5. Customer 2 order ready
   → Grab from queue, serve customer 2

Barista work one task at time.
But move very fast. Seem like doing all at once.
```

Event loop = barista. Task queue = order list. Node.js = coffee shop.

### Single Thread Problem

Node.js run on single thread. One piece code run at time.

```
Normal language (blocking):
  User 1 request → Server busy (10 seconds)
                 → User 2 wait
                 → User 3 wait
                 → User 100 wait
```

If use blocking code, other users stuck.

```
Node.js (event loop):
  User 1 request → Start task, move on
  User 2 request → Start task, move on
  User 3 request → Start task, move on
  User 100 request → Start task, move on
  
  Meanwhile...
  User 1 task done → handle response
  User 2 task done → handle response
  etc.
```

Event loop = manage all at once without blocking.

### Why Event Loop Matter

Without event loop:

```javascript
// Code hang forever on slow task
fs.readFileSync('huge-file.txt');  // Block 10 seconds
// Everything wait 10 seconds
```

With event loop:

```javascript
// Code don't wait
fs.readFile('huge-file.txt', () => {
  // Run later when file ready
});
// Continue immediately
```

* * *

## Single Thread Limitation

Node.js only one thread. One line code run at time.

### Thread = What?

Thread = worker. One thread = one worker.

Normal server:
```
10 threads → 10 workers → handle 10 users at once
Each user have own thread
```

Node.js:
```
1 thread → 1 worker → handle 1,000 users at once
But very smart worker (event loop)
```

### Why Single Thread?

Advantage:
- Simple. No complicated multi-thread stuff.
- No thread collision (same variable, two threads edit = crash).
- Event loop handle many users.

Disadvantage:
- Can't do CPU-heavy work (loop 1 billion times = freeze).
- Heavy math block everything.

### The Event Loop Solution

Event loop = cheat. Act like many threads but only one.

```
Event loop work:

Check call stack: anything running?
  Yes → Let it finish
  No → Check task queue

Task queue have jobs?
  Yes → Grab one, run it
  No → Wait

Repeat forever
```

User see many tasks happening. Actually one at time. But so fast, seem parallel.

* * *

## Call Stack vs Task Queue

Two place where code live: call stack and task queue.

### Call Stack

Call stack = where code execute right now.

```javascript
function greet() {
  console.log('Hello');
}

function main() {
  greet();  // Call stack: main → greet
}

main();
```

Call stack during execution:

```
Step 1: main() call
  Stack: [main]

Step 2: main call greet()
  Stack: [main, greet]

Step 3: greet run, print "Hello"
  Stack: [main, greet]

Step 4: greet finish
  Stack: [main]

Step 5: main finish
  Stack: []
```

Call stack is LIFO (last in, first out). Last function added, first to finish.

### Task Queue

Task queue = where async tasks wait. They not run yet.

```javascript
setTimeout(() => {
  console.log('Later');
}, 1000);

console.log('Now');
```

Execution:

```
Step 1: Call setTimeout
  → Callback add to task queue (but not run yet)
  → Timer start (1 second)

Step 2: console.log('Now') run
  Print: "Now"

Step 3: 1 second pass
  → Callback in queue, ready

Step 4: Call stack empty
  → Event loop grab from task queue
  → Run callback

Step 5: Print "Later"
```

### Call Stack vs Task Queue

```
CALL STACK              TASK QUEUE
═════════════════       ════════════════════
Where code run now      Where async wait

LIFO (stack)            FIFO (queue)

Run immediately         Run when stack empty

Normal function         Async callback
console.log             setTimeout
math operation          fs.readFile
                        db.query
```

* * *

## Event Loop Execution Cycle

Event loop = forever loop. Check → run → check → run.

### Step By Step

```
Event Loop Cycle:

1. Check call stack
   → Anything running?
      No → Go to step 2
      Yes → Wait for finish, go to step 2

2. Check task queue
   → Any callback waiting?
      Yes → Grab first callback
            Run it (add to call stack)
            Go to step 1
      No → Go to step 3

3. Check for more callbacks
   → Wait until something in queue
   → Go to step 1

Repeat forever (while Node.js alive)
```

### Code Example: Event Loop in Action

```javascript
console.log('1. Start');

setTimeout(() => {
  console.log('3. Timeout callback');
}, 0);  // Even 0ms, go to queue

console.log('2. End');
```

Output:
```
1. Start
2. End
3. Timeout callback
```

Why?

```
Step 1: console.log('1. Start') run
  Print: "1. Start"
  Call stack: [log]
  
Step 2: setTimeout call
  Callback go to task queue
  Timer (0ms) start immediately
  Call stack: empty

Step 3: console.log('2. End') run
  Print: "2. End"
  Call stack: empty

Step 4: Event loop check
  Call stack: empty
  Task queue: [callback waiting]
  
Step 5: Event loop grab callback from queue
  Run callback (print "3. Timeout callback")

Output:
1. Start
2. End
3. Timeout callback
```

Even `setTimeout(..., 0)` don't run immediately. Callback go to queue. Wait for stack empty.

### Bigger Example

```javascript
console.log('A');

setTimeout(() => {
  console.log('B');
}, 0);

Promise.resolve()
  .then(() => {
    console.log('C');
  });

console.log('D');
```

Output:
```
A
D
C
B
```

Why?

```
Execution:
console.log('A')        → Print "A"
setTimeout(...)         → Callback to task queue
Promise.then(...)       → Callback to microtask queue (different queue)
console.log('D')        → Print "D"

Call stack empty. Event loop check queues:

First: Microtask queue (Promise callbacks run first)
  Run callback → Print "C"

Then: Task queue (Timer callbacks run after)
  Run callback → Print "B"
```

Node.js have two queues actually:
- **Microtask queue** = Promise, async/await (run first)
- **Task queue** = setTimeout, setInterval (run second)

Not too deep. Just know: Promise callback run before setTimeout callback.

* * *

## How Async Operations Work

Async operations use event loop. Start task, don't wait. Later callback fire.

### File Read

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

console.log('1. Start');

fs.readFile('file.txt', (err, data) => {
  console.log('3. File read done');
});

console.log('2. Continue');
```

Output:
```
1. Start
2. Continue
3. File read done
```

Timeline:

```
Step 1: fs.readFile call
  → File reading start (background)
  → Callback add to task queue
  → But not run yet

Step 2: Continue immediately
  → Don't wait for file

Step 3: Call stack empty

Step 4: File reading done
  → Callback ready in queue

Step 5: Event loop run callback
  → Print "3. File read done"
```

### Database Query

```javascript
const db = require('./db');

console.log('1. Query start');

db.query('SELECT * FROM users', (err, result) => {
  console.log('3. Query done:', result);
});

console.log('2. Did not wait');
```

Same pattern:

```
1. Query start (print)
2. Did not wait (print)
3. Query done: [...] (print after query finish)
```

Query happen background. Event loop run callback when done.

### Multiple Async Operations

```javascript
const fs = require('fs').promises;

console.log('1. Start');

async function loadData() {
  const file1 = await fs.readFile('file1.txt');
  const file2 = await fs.readFile('file2.txt');
  
  console.log('3. Both files loaded');
}

loadData();

console.log('2. Continue');
```

Output:
```
1. Start
2. Continue
3. Both files loaded
```

Why "2" before "3"?

```
Step 1: loadData() call
  → First await (readFile)
  → Callback add to queue
  → Function pause (await)
  → Return immediately

Step 2: Continue (print)
  → Call stack empty

Step 3: File 1 ready
  → First await resolve
  → loadData continue
  → Second await (readFile)
  → Another callback add to queue
  → Function pause again

Step 4: File 2 ready
  → Second await resolve
  → Print "3. Both files loaded"
```

Each `await` pause function. Event loop continue other work. When callback ready, function resume.

* * *

## Timers vs I/O Callbacks

Different type async operations. Different timing.

### Timers: setTimeout, setInterval

```javascript
setTimeout(() => {
  console.log('Timer done');
}, 1000);  // Wait 1000ms
```

How it work:

```
Event loop:

1. setTimeout call
   → Add callback to task queue
   → Timer start 1000ms

2. Event loop continue
   → Do other work

3. 1000ms pass
   → Timer ready
   → Callback can run

4. Call stack empty
   → Event loop grab callback
   → Run it

5. Print "Timer done"
```

Timers = time-based. Wait X milliseconds.

### I/O Callbacks: File read, Database

```javascript
fs.readFile('file.txt', () => {
  console.log('File done');
});
```

How it work:

```
Event loop:

1. fs.readFile call
   → File reading start (OS handle)
   → Callback add to queue (but not ready yet)

2. Event loop continue
   → Do other work

3. OS finish reading file
   → Callback marked ready
   → (Might be 10ms, might be 1 second)

4. Call stack empty
   → Event loop grab callback
   → Run it

5. Print "File done"
```

I/O = depends on OS. Could be slow. Could be fast. Event loop not know when.

### Difference

```
TIMERS                      I/O
══════════════════          ════════════════════
Known wait time             Unknown wait time
Wait X milliseconds         Wait until OS done

setTimeout(...)             fs.readFile
setInterval(...)            db.query
                            http.get

Useful for delays           Useful for reading
Useful for retry            Useful for querying
```

Both use event loop. Both don't block. But different reason when callback fire.

* * *

## Event Loop and Scalability

Event loop = why Node.js fast for many users.

### The Problem: Threads

Traditional server (many threads):

```
100 users → 100 threads
Each thread = memory
Each thread = overhead

Total memory = high
```

Creates 100 threads = slow.
Handle 100 users = fine.
Handle 1000 users = system crash (not enough memory).

### The Solution: Event Loop

Node.js (one thread, event loop):

```
1000 users → 1 thread + event loop
Handle first user → task go to queue
Handle second user → task go to queue
Handle third user → task go to queue
...

Event loop manage all at once.
Total memory = low
Handle 1000 users = easy
Handle 10,000 users = still working
```

Event loop = memory efficient.

### Why Event Loop Scale Better

```
User come:
  Server start async operation
  User task go to queue
  Server immediately free (handle next user)

Meanwhile:
  User 1 operation running (background)
  User 2 operation running (background)
  User 3 operation running (background)
  ...

When operation done:
  Callback fire
  Server send response to user
```

Same 1 thread. Same code. But handle many users.

### Example: Real Server Load

```javascript
const express = require('express');
const app = express();

app.get('/user/:id', async (req, res) => {
  // 1. Start database query
  const user = await db.getUser(req.params.id);
  
  // 2. Query happen background (not blocking)
  
  // 3. When ready, callback fire
  
  // 4. Send response
  res.json(user);
});

app.listen(3000);
```

Handle 1000 requests:

```
Request 1 → Start DB query (background)
Request 2 → Start DB query (background)
Request 3 → Start DB query (background)
...
Request 1000 → Start DB query (background)

All queries run parallel (in background).
Server only use 1 thread.
Memory = low.
Speed = fast.

When Request 1 query done → Send response
When Request 2 query done → Send response
etc.
```

Without event loop:
- Request 1 query (5 seconds)
- Request 2 wait
- Request 3 wait
- ... 1000 requests wait
- Total time = 5000 seconds (impossible)

With event loop:
- All 1000 queries at same time
- Total time = ~5 seconds

That why Node.js famous for scaling.

* * *

## Event Loop Problems (CPU-Heavy Code)

Event loop great for I/O. Bad for CPU-heavy code.

### Problem: Long Running Code

```javascript
app.get('/calculate', (req, res) => {
  // CPU heavy - loop 1 billion times
  let sum = 0;
  for (let i = 0; i < 1000000000; i++) {
    sum += i;
  }
  
  res.json({ sum });
});
```

What happen:

```
User 1 request
  → Start calculate loop
  → Block event loop (3 seconds)
  
During those 3 seconds:
  User 2 request (wait)
  User 3 request (wait)
  User 4 request (wait)
  
User 1 response send (3 seconds later)
  → Now User 2 can start
  → Now User 3 can start
```

CPU-heavy code = freeze event loop. Other users suffer.

### Solution: Worker Threads

For heavy work, use worker threads (separate thread):

```javascript
const { Worker } = require('worker_threads');

app.get('/calculate', (req, res) => {
  const worker = new Worker('./calculate.js');
  
  worker.on('message', (result) => {
    res.json({ sum: result });
  });
});
```

Worker thread do heavy work. Main thread (event loop) continue serve other users.

But mostly: avoid CPU-heavy code on server. Use Node.js for I/O, not math.

* * *

## Event Loop in Real Code

### Example 1: Simple Request

```javascript
const express = require('express');
const app = express();

app.get('/hello', (req, res) => {
  console.log('Request come');
  res.json({ message: 'Hello' });
});

app.listen(3000);
```

Timeline:

```
User 1 request come
  → /hello route handler run
  → Print "Request come"
  → Send response
  → Handler finish (remove from stack)

User 2 request come
  → /hello route handler run
  → Print "Request come"
  → Send response
  → Handler finish

Event loop continue... always
```

Each request = separate handler. Event loop run one, then next.

### Example 2: Database Request

```javascript
const express = require('express');
const app = express();

app.get('/user/:id', async (req, res) => {
  console.log('1. Query start');
  
  const user = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
  
  console.log('2. Query done');
  res.json(user);
});

app.listen(3000);
```

Timeline:

```
User 1 request
  → Print "1. Query start"
  → await (pause here)
  → Query add to queue
  → Handler pause

User 2 request (meanwhile)
  → Print "1. Query start"
  → await (pause here)
  → Query add to queue
  → Handler pause

User 1 query done
  → Callback fire
  → Print "2. Query done"
  → Send response

User 2 query done
  → Callback fire
  → Print "2. Query done"
  → Send response
```

Both queries happen at same time. Event loop manage both.

### Example 3: File Operations

```javascript
const fs = require('fs').promises;

async function processFiles() {
  console.log('1. Start');
  
  const files = await Promise.all([
    fs.readFile('file1.txt'),
    fs.readFile('file2.txt'),
    fs.readFile('file3.txt')
  ]);
  
  console.log('2. All done');
}

processFiles();
console.log('3. Continue');
```

Output:
```
1. Start
3. Continue
2. All done
```

Timeline:

```
processFiles() call
  → Print "1. Start"
  → Promise.all start all 3 reads
  → await (pause function)

Print "3. Continue"
  → Main code continue

File reads happen background:
  OS read file 1
  OS read file 2
  OS read file 3

All 3 files done
  → Promise.all resolve
  → Function resume
  → Print "2. All done"
```

One thread. Three file reads at same time. Event loop magic.

* * *

## Event Loop Visualization

### Step-by-Step Flow

```
START
  │
  ├─→ Check Call Stack
  │   │
  │   ├─ Something running?
  │   │  Yes → Wait for finish
  │   │  No → Go to next
  │   │
  │
  ├─→ Check Microtask Queue (Promise)
  │   │
  │   ├─ Anything waiting?
  │   │  Yes → Run it, go back to stack check
  │   │  No → Go to next
  │   │
  │
  ├─→ Check Task Queue (setTimeout, I/O)
  │   │
  │   ├─ Anything waiting?
  │   │  Yes → Run it, go back to stack check
  │   │  No → Sleep until something arrive
  │   │
  │
  └─→ Loop again (forever)
```

### Code Execution Order

```javascript
// What print first?

console.log('A');                    // Call stack run

setTimeout(() => {
  console.log('B');                  // Task queue (timer)
}, 0);

Promise.resolve().then(() => {
  console.log('C');                  // Microtask queue (Promise)
});

console.log('D');                    // Call stack run
```

Order:

```
1. A, D (Call stack, synchronous code)
2. C    (Microtask queue, Promises)
3. B    (Task queue, Timers)

Output:
A
D
C
B
```

Event loop priority:
1. Synchronous code (call stack)
2. Promises/async (microtask queue)
3. Timers/I/O (task queue)

* * *

## Common Event Loop Mistakes

### Mistake 1: Think Await Block Other Users

```javascript
// ✗ Think this block other users
app.get('/user/:id', async (req, res) => {
  const user = await db.getUser(id);  // Blocking?
  res.json(user);
});

// ✓ Reality: don't block others
// await pause THIS function
// But event loop continue handle OTHER users
```

Each user have own context. Await pause one context, not all.

### Mistake 2: Forget Event Loop on CPU Work

```javascript
// ✗ Block event loop (bad for other users)
for (let i = 0; i < 1000000000; i++) {
  sum += i;  // CPU heavy
}

// ✓ Use worker thread or break into chunks
const chunk = async () => {
  sum += i;
  // resume later
};
```

CPU work can't use async. Must use worker threads or split work.

### Mistake 3: Assume setTimeout(0) = Immediate

```javascript
// ✗ Think callback run immediately
setTimeout(() => {
  console.log('Immediate?');
}, 0);

// ✓ Reality: callback wait in queue
// Go to task queue
// Run when stack empty
// NOT immediately
```

Even 0ms don't mean immediately. Go to queue. Might wait milliseconds.

### Mistake 4: Not Understand Microtask Queue

```javascript
// ✗ Think Promise and setTimeout same
console.log('A');

setTimeout(() => console.log('B'), 0);
Promise.resolve().then(() => console.log('C'));

console.log('D');

// ✗ Expect: A, B, C, D
// ✓ Real: A, D, C, B
```

Promise use microtask queue (run first).
setTimeout use task queue (run second).

Different queues = different timing.

* * *

## Practice Assignment

**1. Event loop order:**

```javascript
// Predict output order
console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve()
  .then(() => console.log('3'))
  .then(() => console.log('4'));

console.log('5');

// What order? Why?
// Test and verify
```

**2. Async operations:**

```javascript
// Write function that:
// - Read 3 files
// - Do all 3 at same time (parallel)
// - Print results when all done
// Use Promise.all or async/await
```

**3. Understand blocking:**

```javascript
// Create two endpoints:
// - One with CPU loop (block)
// - One without block

// Test: which handle user faster?
// Monitor: does one slow down other?
```

**4. Event loop trace:**

```javascript
// Trace execution step by step

app.get('/test', async (req, res) => {
  console.log('A');
  
  const file = await fs.readFile('file.txt');
  
  console.log('B');
  res.json(file);
});

// When do A and B print?
// What if 3 users request at same time?
```

**5. Fix scalability:**

```javascript
// Given slow endpoint
// Rewrite to use event loop better
// Should handle 1000 users, not 10

// Before: server crash with 1000 users
// After: server handle 1000 users easy
```

* * *

## Quick Recap

*   **Event loop** = manager. Check what ready, run it, repeat.
    
*   **Single thread** = one code run at time. But event loop make seem parallel.
    
*   **Call stack** = where code run now. LIFO order.
    
*   **Task queue** = where async callback wait. FIFO order.
    
*   **Event loop cycle**: Check stack → Check queue → Run task → Repeat.
    
*   **Async operations** = start, don't wait. Callback fire later.
    
*   **Microtask queue** = Promise callbacks. Run before task queue.
    
*   **Task queue** = setTimeout, I/O callbacks. Run after microtask.
    
*   **Timers** = time-based. Wait X milliseconds.
    
*   **I/O callbacks** = depends on OS. When file/DB ready.
    
*   **Scalability** = event loop handle many users with 1 thread.
    
*   **No threads needed** = event loop do work of many threads.
    
*   **Memory efficient** = 1 thread vs 100 threads.
    
*   **CPU-heavy code** = freeze event loop. Use worker threads.
    
*   **Await pause function** = not block event loop. Other users continue.
    
*   **setTimeout(0) != immediate** = go to task queue. Wait until stack empty.
    
*   **Promise run first** = microtask queue before task queue.
    
*   **Without event loop** = Node.js can't scale. Dead project.
    
*   **With event loop** = 1000 users, 1 thread. That magic.

Event loop = why Node.js exist. Without it, just another server language.

* * *

> If you enjoyed this article, check out my other blogs on this profile. Connect with me: [LinkedIn](https://linkedin.com/in/rajharsh03) | [GitHub](https://github.com/RajHarsh03) | [X (Twitter)](https://x.com/rajharsh03)
