How Node.js Handles Multiple Requests with a Single Thread

Search for a command to run...

No comments yet. Be the first to comment.
You open WhatsApp, type a message, and hit send. A single grey tick appears. You're on a train with no signal, but the message went through anyway. Or did it? That single tick is the beginning of a su

You record a Reel, add a song, trim it, and then close the app before posting. When you reopen Instagram, the draft is right there waiting. Nothing was lost. That experience feels simple. The system b

If you have built a React Native app, you have felt the pain of navigation setup. It is easy to get wrong and hard to keep clean as apps grow. This article explains what routing means, why it matters,

Modern apps are not just a pile of screens. They are a living system of features, data flows, offline behavior, and performance constraints. If you have only shipped small apps, the jump to something

Your application has user accounts. Some routes should only be accessible to logged-in users. How do you protect routes and verify that a request actually comes from who they claim to be? JWT (JSON We

Ashish's Blog
57 posts
One of the most confusing things about Node.js is that it runs on a single thread, yet it can handle thousands of concurrent requests. This seems like a contradiction. How does a single thread handle multiple requestsa without blocking? The answer lies in understanding concurrency versus parallelism and the event loop.
Node.js runs your JavaScript code on a single thread. There's only one thread executing your code at any given moment.
Contrast this with languages like Java or Python, where you might create a new thread for each request. With Node.js, you can't do that. You get one thread, and that's it.
If you tried to handle requests synchronously and sequentially, your server would be painfully slow. The first request would block all others.
This is critical to understand:
Parallelism means doing multiple things at the exact same time. You need multiple processors or cores.
Concurrency means managing multiple tasks without necessarily doing them at the same time. You interleave tasks.
Node.js achieves concurrency, not parallelism, through its single thread.
Think of a restaurant again. One waiter (single thread) can handle many customers (requests) concurrently by:
Taking customer A's order
Passing it to the kitchen (delegating work)
Taking customer B's order
Checking if customer A's food is ready
Serving customer A
Taking customer C's order
Checking if customer B's food is ready
Serving customer B
The waiter isn't cooking (just like Node.js doesn't perform database queries itself). The waiter is coordinating and serving, while the kitchen does the actual work.
When you perform slow operations like reading files or querying databases, Node.js doesn't have your thread wait. Instead, it delegates the work to background workers.
Here's what happens:
import fs from 'fs';
console.log('Starting');
fs.readFile('large-file.txt', 'utf8', (err, data) => {
console.log('File read complete');
});
console.log('Continuing');
Execution flow:
"Starting" is logged
readFile is called, work is delegated to a background worker
"Continuing" is logged immediately (no wait!)
Background worker reads the file
Once complete, the callback is queued
Event loop picks up the callback and executes it
"File read complete" is logged
Output:
Starting
Continuing
File read complete
Your thread never blocks. It quickly delegates work and moves on to handle other things.
Here's a more realistic server example:
import express from 'express';
import fs from 'fs';
const app = express();
app.get('/read-file', (req, res) => {
fs.readFile('data.txt', 'utf8', (err, data) => {
if (err) {
res.status(500).send('Error reading file');
} else {
res.send(data);
}
});
});
app.get('/db-query', (req, res) => {
// Simulating a database query that takes 1 second
setTimeout(() => {
res.send('Query complete');
}, 1000);
});
app.listen(3000, () => console.log('Server running'));
Imagine two requests arrive at almost the same time:
Request 1 comes in → Server starts reading a file → Work delegated to background thread
Request 2 comes in → Server performs a database query → Work delegated to background thread
Node.js thread is free and waiting for events
File read completes → Callback executed → Response sent to client 1
Database query completes → Callback executed → Response sent to client 2
Both operations happened concurrently, but your JavaScript code only ran on one thread. The thread quickly delegates work and moves on.
The event loop continuously checks if there's work to do:
Main Thread (JavaScript execution)
|
-----+-----
| |
Sync Code Callbacks
| |
| Event Loop checks:
| 1. Is there a completed operation?
| 2. Execute its callback
| 3. Go back to step 1
Here's a concrete example:
console.log('1: Start');
setTimeout(() => {
console.log('2: Timer complete');
}, 100);
console.log('3: Still on main thread');
Output:
1: Start
3: Still on main thread
2: Timer complete
Even though the timer was set first, the synchronous code runs first. The callback waits until the main thread is free.
This design is powerful because:
No thread overhead: Creating a thread for each request is expensive. Node.js uses a single thread plus a pool of background workers.
Efficient resource usage: A single thread uses far less memory than hundreds of threads.
Avoids context switching: The operating system doesn't have to switch between many threads.
Non-blocking by default: Slow operations don't block other requests.
A server might handle 10,000 concurrent connections with a single thread because most of that time is spent waiting for databases, files, or network responses—none of which block the thread.
What happens if you ignore this and write blocking code?
import express from 'express';
import fs from 'fs';
const app = express();
app.get('/slow-sync', (req, res) => {
// This blocks the entire server!
const data = fs.readFileSync('large-file.txt', 'utf8');
res.send(data);
});
app.listen(3000);
If a request to /slow-sync takes 2 seconds, every other request waits 2 seconds. The single thread is occupied and can't handle other requests.
With 100 simultaneous requests, they queue up and wait for the thread. This destroys performance.
For CPU-intensive operations (like complex calculations), Node.js has worker threads. You can offload heavy computations to prevent blocking:
import express from 'express';
import { Worker } from 'worker_threads';
import path from 'path';
import { fileURLToPath } from 'url';
const app = express();
const __dirname = path.dirname(fileURLToPath(import.meta.url));
app.get('/cpu-intensive', (req, res) => {
// Delegate to a worker thread
const worker = new Worker(path.join(__dirname, 'worker.js'));
worker.on('message', (result) => {
res.json({ result });
});
worker.postMessage({ data: 'process this' });
});
app.listen(3000);
This keeps the main thread responsive while a worker does the heavy computation.
Node.js runs JavaScript on a single thread, achieving concurrency, not parallelism
Slow operations like I/O are delegated to background workers
While workers are busy, the main thread handles other requests
The event loop coordinates everything
This model scales well and uses resources efficiently
Blocking code (synchronous operations) kills performance
For CPU-intensive tasks, use worker threads
The single-threaded model is a feature, not a limitation
Understanding this is key to writing efficient Node.js applications. Don't block the thread, let it coordinate work, and your server will handle massive concurrent load.