What is Node.js and how does it handle concurrency with its event-driven architecture?

⚙️ Backend Development9/21/2025
Understanding Node.js runtime, event loop, non-blocking I/O, and how it achieves high concurrency with single-threaded architecture.

Node.js Overview

What is Node.js?

Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine that allows running JavaScript on the server-side.

Key Characteristics

1. Event-Driven Architecture

  • Event Loop: Single-threaded event loop
  • Non-blocking I/O: Asynchronous operations
  • Callbacks/Promises: Handle asynchronous results
  • Event Emitters: Publish-subscribe pattern

2. Single-Threaded (Main Thread)

// Non-blocking file read
const fs = require('fs');

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

console.log('This runs immediately');
// Output: "This runs immediately" then "File read complete"

Event Loop Phases

1. Timer Phase

  • Executes callbacks scheduled by setTimeout() and setInterval()

2. Pending Callbacks Phase

  • Executes I/O callbacks deferred to the next loop iteration

3. Idle, Prepare Phase

  • Internal use only

4. Poll Phase

  • Fetches new I/O events
  • Executes I/O-related callbacks

5. Check Phase

  • setImmediate() callbacks executed here

6. Close Callbacks Phase

  • Cleanup callbacks (e.g., socket.on('close', ...))

Concurrency Model

How Node.js Handles 10,000+ Connections

const http = require('http');

const server = http.createServer((req, res) => {
    // Each request doesn't block others
    // Non-blocking I/O operations
    
    // Simulating async database call
    setTimeout(() => {
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ message: 'Hello World' }));
    }, 100);
});

server.listen(3000);
console.log('Server running on port 3000');

Thread Pool for Heavy Operations

const fs = require('fs');
const crypto = require('crypto');

// These operations use thread pool
fs.readFile('file.txt', callback);     // File I/O
crypto.pbkdf2('secret', 'salt', 100000, 64, 'sha512', callback); // CPU-intensive

Advantages

  1. High Concurrency: Handle many connections with low overhead
  2. Fast I/O: Non-blocking operations
  3. JavaScript Everywhere: Same language for frontend and backend
  4. Large Ecosystem: NPM package manager
  5. Real-time Applications: WebSockets, chat applications

Disadvantages

  1. CPU-Intensive Tasks: Blocks the event loop
  2. Single Point of Failure: Main thread crash affects entire application
  3. Callback Hell: Complex nested callbacks (solved with Promises/async-await)
  4. Memory Leaks: Potential with event listeners

Best Practices

1. Avoid Blocking the Event Loop

// BAD: Blocking operation
function fibonacciSync(n) {
    if (n <= 1) return n;
    return fibonacciSync(n - 1) + fibonacciSync(n - 2);
}

// GOOD: Non-blocking with Worker Threads
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

if (isMainThread) {
    const worker = new Worker(__filename, { workerData: 40 });
    worker.on('message', (result) => {
        console.log('Fibonacci result:', result);
    });
} else {
    const result = fibonacci(workerData);
    parentPort.postMessage(result);
}

2. Handle Errors Properly

// Unhandled promise rejection handling
process.on('unhandledRejection', (reason, promise) => {
    console.error('Unhandled Rejection at:', promise, 'reason:', reason);
    process.exit(1);
});

// Uncaught exception handling
process.on('uncaughtException', (err) => {
    console.error('Uncaught Exception:', err);
    process.exit(1);
});

3. Use Streams for Large Data

const fs = require('fs');

// BAD: Loads entire file into memory
fs.readFile('large-file.txt', (err, data) => {
    // Process data
});

// GOOD: Process data in chunks
const stream = fs.createReadStream('large-file.txt');
stream.on('data', (chunk) => {
    // Process chunk
});
stream.on('end', () => {
    console.log('File processing complete');
});

Performance Optimization

1. Clustering

const cluster = require('cluster');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
    // Fork workers
    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }
    
    cluster.on('exit', (worker, code, signal) => {
        console.log(`Worker ${worker.process.pid} died`);
        cluster.fork(); // Restart worker
    });
} else {
    // Worker process
    require('./app.js');
}

2. Connection Pooling

const mysql = require('mysql2');

// Connection pool for database
const pool = mysql.createPool({
    host: 'localhost',
    user: 'user',
    password: 'password',
    database: 'mydb',
    connectionLimit: 10,
    queueLimit: 0
});

Use Cases

Good For:

  • Real-time applications (chat, gaming)
  • REST APIs and microservices
  • Single Page Applications (SPAs)
  • Streaming applications
  • IoT applications

Not Ideal For:

  • CPU-intensive applications
  • Heavy computational tasks
  • Applications requiring high precision
By: System Admin