What is Node.js and how does it handle concurrency with its event-driven architecture?
⚙️ Backend Development• 9/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()
andsetInterval()
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
- High Concurrency: Handle many connections with low overhead
- Fast I/O: Non-blocking operations
- JavaScript Everywhere: Same language for frontend and backend
- Large Ecosystem: NPM package manager
- Real-time Applications: WebSockets, chat applications
Disadvantages
- CPU-Intensive Tasks: Blocks the event loop
- Single Point of Failure: Main thread crash affects entire application
- Callback Hell: Complex nested callbacks (solved with Promises/async-await)
- 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