How Can a Million Requests Call the Same Singleton Function in Node.js?
Great question. It might seem surprising at first, but Node.js can efficiently handle millions of concurrent requests — even when they all call the same method on a Singleton — thanks to its event-driven, non-blocking architecture.
Let’s break it down step by step.
1. Node.js and the Event Loop
Node.js uses a single-threaded event loop, but that doesn’t mean it handles only one request at a time.
Instead, it relies on asynchronous I/O operations and event callbacks to handle multiple concurrent requests without blocking the main thread.
Here’s what happens when a request comes in:
- Node.js begins processing the request.
- If the request involves asynchronous operations (like database queries or file reads), those tasks are delegated (to the system kernel or worker threads).
- While waiting, Node.js moves on to handle the next request.
- When the async operation completes, a callback is queued and executed.
This architecture allows Node.js to handle a massive number of simultaneous requests using minimal resources.
2. Shared Singleton Function Access
Now let’s say we have a simple Singleton object like this:
class Config {
constructor() {
this.settings = {
dbHost: 'localhost',
dbPort: 3306
};
}
get(key) {
return this.settings[key];
}
}
const config = new Config();
module.exports = config;- This
configobject is created once, at application startup. - Every request that imports this module receives the same instance.
- The
get()method simply reads from a static in-memory object — no mutation, no I/O, no blocking.
So what happens if a million requests all call config.get()?
They all succeed, with zero conflict.
That’s because:
- There’s no shared state being mutated.
- No blocking or waiting is involved.
- Each call is lightweight and independent.
3. Node.js Concurrency in Action
Let’s put it into context with a simple Express server:
const express = require('express');
const app = express();
const config = require('./config');
app.get('/', (req, res) => {
const dbHost = config.get('dbHost');
res.send(`Database host is ${dbHost}`);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});If 1 million requests hit this server:
- Each one will call
config.get('dbHost'). - This is just a property lookup in a JavaScript object.
- Node.js handles these in the event loop, efficiently queuing and responding to requests without waiting for previous ones to finish.
Because the method is:
- Stateless
- Read-only
- Non-blocking
So, Node.js can process requests concurrently with minimal delay.
4. When a Singleton Becomes a Bottleneck
This efficiency only holds as long as the Singleton stays stateless and non-blocking.
Here’s what can go wrong:
Blocking Operation Example
class Logger {
constructor() {
this.logFile = fs.createWriteStream('./app.log');
}
log(message) {
this.logFile.write(`${new Date().toISOString()} ${message}\n`);
}
}If every request writes a log entry:
- All requests share the same
Loggerinstance and the same file stream. - Writing to disk is I/O-bound and potentially blocking.
- Under high load, log writes can slow down or block the event loop.
Shared Mutable State Example
class EmailSender {
constructor() {
this.recipients = [];
}
setRecipients(list) {
this.recipients = list;
}
send(message) {
this.recipients.forEach(email => {
console.log(`Sending "${message}" to ${email}`);
});
}
}If multiple requests modify the recipients list at the same time, they’ll interfere with one another — a classic race condition.
5. Conclusion
Yes — a million requests can safely call the same function in a Singleton, as long as:
✅ The method is read-only
✅ There is no shared mutable state
✅ There is no blocking operation (like file or network I/O)
✅ The function operates in constant time and memory
Node.js can handle high concurrency not by creating threads, but by queuing and dispatching work through the event loop in a highly efficient, non-blocking manner.
Key Takeaway
A Singleton in Node.js is safe and scalable if it’s stateless and performs non-blocking operations. But once it introduces mutable data or blocking logic, it can quietly become a performance bottleneck — or worse, a source of bugs.
