# Blocking vs Non-Blocking Code in Node.js

One fundamental concept separates great Node.js developers from frustrated ones: understanding blocking vs non-blocking code. Write blocking code, and your entire server freezes. Write non-blocking code, and it handles thousands of concurrent users.

## What Is Blocking Code?

Blocking code stops execution until an operation completes.

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

console.log('Starting');
const data = fs.readFileSync('file.txt'); // Waits here
console.log('File read');
console.log(data);
```

While `readFileSync` reads the file, nothing else happens. Your code is frozen. If this is a server handling requests, every other request waits.

Execution timeline:

```plaintext
0ms: Start
0ms: Call readFileSync
100ms: File read, execution continues
100ms: Log file content
```

The server is blocked for 100ms. If you have 100 simultaneous requests, they queue up for 100ms each. Total wait time: 10,000ms (10 seconds).

## What Is Non-Blocking Code?

Non-blocking code delegates work and continues immediately:

```javascript
import fs from 'fs';

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

Output:

```plaintext
Starting
Continuing
File read
[file content]
```

The file read happens in the background. Your code continues immediately.

Execution timeline:

```plaintext
0ms: Start
0ms: Delegate file read
0ms: Log "Continuing"
100ms: File read completes, callback executes
```

The server never blocks. It can handle other requests while waiting.

## Real-World Impact

Let's simulate a server handling three requests, each requiring a 1-second file read:

**Blocking Code:**

```javascript
app.get('/data', (req, res) => {
  const data = fs.readFileSync('file.txt'); // Blocks for 1 second
  res.send(data);
});
```

Timeline:

```plaintext
0s: Request 1 arrives, starts reading
1s: Request 1 done, Response 1 sent
1s: Request 2 arrives (was waiting), starts reading
2s: Request 2 done, Response 2 sent
2s: Request 3 arrives (was waiting), starts reading
3s: Request 3 done, Response 3 sent
```

Total time for all users: 3 seconds. Avg user wait: 2 seconds.

**Non-Blocking Code:**

```javascript
app.get('/data', (req, res) => {
  fs.readFile('file.txt', (err, data) => {
    res.send(data);
  });
});
```

Timeline:

```plaintext
0s: Request 1 arrives, delegates read, returns immediately
0s: Request 2 arrives, delegates read, returns immediately
0s: Request 3 arrives, delegates read, returns immediately
1s: All reads complete, all responses sent
```

Total time for all users: 1 second. Avg user wait: ~1 second.

Non-blocking is 3x faster with just 3 requests. With 100 requests, the difference is massive.

## Blocking Operations in Node.js

Common operations that have synchronous (blocking) versions:

```javascript
import fs from 'fs';

// Blocking
fs.readFileSync('file.txt');
fs.writeFileSync('file.txt', 'data');

// Non-blocking alternatives
fs.readFile('file.txt', (err, data) => {});
fs.writeFile('file.txt', 'data', (err) => {});
```

**Database operations:**

```javascript
// Blocking (bad)
const result = db.querySync('SELECT * FROM users');

// Non-blocking (good)
db.query('SELECT * FROM users', (err, result) => {});
```

**HTTP requests:**

```javascript
// Blocking (bad - if such a function existed)
const response = http.getSync('https://api.example.com/data');

// Non-blocking (good)
fetch('https://api.example.com/data')
  .then(res => res.json())
  .then(data => console.log(data));
```

## Why Does Blocking Happen?

Some operations in Node.js are inherently slow:

*   Reading from disk (slower than RAM)
    
*   Network requests (very slow)
    
*   Database queries (slow)
    
*   Complex calculations (time-consuming)
    

Node.js can't speed these up. But it can handle them without blocking the thread by delegating to background workers.

## The Event Loop Revisited

Understanding the event loop explains the difference:

```javascript
import fs from 'fs';

console.log('1');
fs.readFile('file.txt', () => {
  console.log('2');
});
console.log('3');
```

Output:

```plaintext
1
3
2
```

Here's what happens:

```plaintext
1. JavaScript starts
   - Logs "1"
   - Delegates file read (tells worker thread)
   - Logs "3"
   - No more code

2. Event loop checks: Is anything ready?
   - File read not done yet, nothing to do

3. Event loop checks: Is anything ready?
   - File is ready! Execute callback
   - Logs "2"
```

The callback waits in a queue until the main thread is free.

## Async/Await: Cleaner Syntax

While callbacks are non-blocking, async/await is cleaner:

```javascript
import fs from 'fs/promises';

// Using callbacks (non-blocking)
fs.readFile('file.txt', (err, data) => {
  console.log(data);
});

// Using async/await (still non-blocking!)
const data = await fs.readFile('file.txt');
console.log(data);
```

Async/await looks like synchronous code but it's non-blocking. When you `await` a slow operation, the function pauses, but the thread continues handling other requests.

## Practical Examples

**Bad: Synchronous (blocking)**

```javascript
import express from 'express';
import fs from 'fs';

const app = express();

app.get('/read-file', (req, res) => {
  const data = fs.readFileSync('large-file.txt'); // Blocks entire server
  res.send(data);
});

app.listen(3000);
```

**Good: Asynchronous (non-blocking)**

```javascript
import express from 'express';
import fs from 'fs/promises';

const app = express();

app.get('/read-file', async (req, res) => {
  try {
    const data = await fs.readFile('large-file.txt');
    res.send(data);
  } catch (error) {
    res.status(500).send('Error reading file');
  }
});

app.listen(3000);
```

**Bad: Database operations (blocking)**

```javascript
app.get('/users', (req, res) => {
  const users = db.querySync('SELECT * FROM users'); // Blocks server
  res.json(users);
});
```

**Good: Database operations (non-blocking)**

```javascript
app.get('/users', async (req, res) => {
  const users = await db.query('SELECT * FROM users');
  res.json(users);
});
```

## Mixing Blocking and Non-Blocking

If even one route is blocking, it can impact others:

```javascript
import express from 'express';
import fs from 'fs';
import fs_async from 'fs/promises';

const app = express();

// This route blocks the entire server
app.get('/slow', (req, res) => {
  const data = fs.readFileSync('huge-file.txt'); // BLOCKS
  res.send(data);
});

// This route is fast but blocked by /slow
app.get('/fast', (req, res) => {
  res.send('Fast response');
});

// If someone calls /slow, requests to /fast must wait!
```

One blocking operation can bring down your entire server.

## CPU-Intensive Code

Even pure JavaScript computations can block:

```javascript
// Blocks the thread for 5 seconds
function expensiveCalculation() {
  let result = 0;
  for (let i = 0; i < 1_000_000_000; i++) {
    result += Math.sqrt(i);
  }
  return result;
}

app.get('/calculate', (req, res) => {
  const result = expensiveCalculation(); // Blocks thread
  res.json({ result });
});
```

For CPU-intensive work, use worker threads:

```javascript
import { Worker } from 'worker_threads';
import path from 'path';

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

## Common Mistakes

**Mistake 1: Using** `readFileSync` **in a server**

```javascript
// Wrong - blocks entire server
app.get('/data', (req, res) => {
  const data = fs.readFileSync('file.txt');
  res.send(data);
});

// Right - non-blocking
app.get('/data', async (req, res) => {
  const data = await fs.readFile('file.txt');
  res.send(data);
});
```

**Mistake 2: Not awaiting async operations**

```javascript
// Wrong - doesn't wait for query
app.get('/users', async (req, res) => {
  const users = db.query('SELECT * FROM users');
  res.json(users); // users is undefined
});

// Right - waits for query
app.get('/users', async (req, res) => {
  const users = await db.query('SELECT * FROM users');
  res.json(users);
});
```

**Mistake 3: Blocking in middleware**

```javascript
// Wrong - blocking middleware
app.use((req, res, next) => {
  const user = db.querySync('SELECT * FROM users WHERE id=1');
  req.user = user;
  next();
});

// Right - non-blocking middleware
app.use(async (req, res, next) => {
  req.user = await db.query('SELECT * FROM users WHERE id=1');
  next();
});
```

## Key Takeaways

*   Blocking code stops execution until operations complete
    
*   Non-blocking code delegates work and continues immediately
    
*   In a server, one blocking operation can block all other requests
    
*   Node.js provides non-blocking alternatives to synchronous functions
    
*   Use `async/await` for cleaner non-blocking code
    
*   Avoid `.Sync` methods (readFileSync, etc.) in servers
    
*   Even one blocking route can cripple server performance
    
*   CPU-intensive tasks should use worker threads
    

Embracing non-blocking code is what separates Node.js code that barely works from code that scales massively. Master this concept, and you've mastered Node.js.
