1. Explain the phases of the Node.js event loop
Node.js uses an event loop to handle asynchronous operations. It runs in multiple phases, each with a specific purpose.
Main phases:
- Timers phase
Executes callbacks scheduled bysetTimeout()andsetInterval(). - Pending Callbacks phase
Executes I/O callbacks deferred from the previous cycle. - Idle, Prepare phase
Internal use by Node.js. - Poll phase
- Retrieves new I/O events
- Executes I/O callbacks
- Waits for new events if none exist
- Check phase
Executes callbacks fromsetImmediate(). - Close Callbacks phase
Executes close events likesocket.on('close').
Flow summary:
Timers → Pending → Poll → Check → Close → repeat
2. Difference between process.nextTick() and setImmediate()
| process.nextTick() | setImmediate() |
|---|---|
| Executes immediately after current operation | Executes in check phase |
| Higher priority | Lower priority |
| Runs before event loop continues | Runs in next iteration |
Example:
setImmediate(() => console.log("setImmediate"));
process.nextTick(() => console.log("nextTick"));
console.log("Main");
Output:
Main
nextTick
setImmediate
3. Difference between setImmediate() and setTimeout()
| setImmediate() | setTimeout() |
|---|---|
| Runs in check phase | Runs in timers phase |
| Executes after I/O events | Executes after delay |
| More predictable after I/O | Depends on timer delay |
Example:
setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));
Note:
Order may vary depending on context, but inside I/O, setImmediate usually runs first.
4. How does Node.js prioritize async operations?
Node.js prioritizes tasks using:
- process.nextTick queue (highest priority)
- Microtask queue (Promises)
- Event loop phases (timers, I/O, etc.)
Priority order:
- process.nextTick
- Promise callbacks
- setTimeout / setImmediate
Example:
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("promise"));
process.nextTick(() => console.log("nextTick"));
Output:
nextTick
promise
timeout
5. What happens if the event loop is blocked?
If the event loop is blocked:
- No other tasks can be processed
- Requests are delayed
- Application becomes unresponsive
Example:
while (true) {
// infinite loop blocks event loop
}
Impact:
- Server cannot handle requests
- Performance degrades severely
6. How do you detect event loop blocking?
Methods:
- Measure delay using timers
const start = Date.now();
setTimeout(() => {
console.log(Date.now() - start);
}, 100);
If delay is large → event loop is blocked.
- Use performance tools
-
clinic.js -
node --inspect - Monitoring tools
- Use built-in metrics
-
process.hrtime() - Event loop lag monitoring
7. What is starvation in Node.js?
Starvation occurs when some tasks are continuously delayed or never executed because higher-priority tasks keep running.
Example:
function loop() {
process.nextTick(loop);
}
loop();
This blocks other operations from executing.
8. What is libuv?
libuv is a C library used by Node.js to handle:
- Asynchronous I/O
- Event loop
- Thread pool
Responsibilities:
- File system operations
- Networking
- Thread management
9. How does libuv interact with the OS?
libuv interacts with the operating system using:
- System calls for I/O
- OS event notification systems (like epoll, kqueue, IOCP)
- Thread pool for blocking operations
Flow:
- Node.js delegates task to libuv
- libuv interacts with OS
- OS completes task
- libuv sends result back to event loop
10. What is a thread pool?
A thread pool is a group of worker threads used to handle blocking or heavy tasks.
In Node.js, it is managed by libuv.
Used for:
- File system operations
- DNS lookups
- Crypto operations
Default size:
- 4 threads (can be increased)
Example concept:
const fs = require('fs');
fs.readFile('file.txt', () => {
console.log("Handled by thread pool");
});
12. Which library manages the thread pool in Node.js?
The thread pool in Node.js is managed by libuv.
Key points:
- libuv is a C library used internally by Node.js
- It provides:
- Thread pool
- Event loop
- Async I/O handling
Default behavior:
- Thread pool size = 4 (can be changed using
UV_THREADPOOL_SIZE)
13. How does Node.js handle I/O operations internally?
Node.js handles I/O using non-blocking, asynchronous mechanisms powered by libuv.
Internal flow:
- Request comes to Node.js
- If I/O operation is needed:
- Delegated to OS or libuv thread pool
- Node.js continues executing other tasks
- Once operation completes:
- Callback is pushed to event queue
- Event loop executes callback
Example:
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
console.log(data);
});
console.log("Non-blocking execution");
14. Is Node.js truly single-threaded?
Node.js is single-threaded for JavaScript execution, but not entirely single-threaded internally.
Explanation:
- Main thread → runs JavaScript (event loop)
- Background threads → used by libuv
So:
- Single-threaded execution model
- Multi-threaded internally (for I/O, thread pool)
15. What are worker threads?
Worker threads allow Node.js to run JavaScript code in parallel threads.
They are part of the worker_threads module.
Purpose:
- Handle CPU-intensive tasks
- Avoid blocking the event loop
Example:
const { Worker } = require('worker_threads');
new Worker(`
console.log("Running in worker thread");
`, { eval: true });
16. When should you use worker threads?
Use worker threads when:
- CPU-intensive tasks
- Image processing
- Data computation
- Encryption
Do NOT use for:
- I/O operations (Node already handles efficiently)
Reason:
Worker threads prevent blocking the main event loop.
17. What is clustering in Node.js?
Clustering allows you to create multiple Node.js processes to utilize multi-core CPUs.
It uses the cluster module.
Key idea:
- Each process runs independently
- All share same server port
Example:
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const cpus = os.cpus().length;
for (let i = 0; i < cpus; i++) {
cluster.fork();
}
} else {
require('./server');
}
18. How does clustering improve performance?
Clustering improves performance by:
- Utilizing multiple CPU cores
- Distributing load across processes
- Increasing throughput
- Improving fault tolerance
Without clustering:
- Only one core used
With clustering:
- Multiple cores used
19. Difference between cluster and worker threads
| Cluster | Worker Threads |
|---|---|
| Uses multiple processes | Uses multiple threads |
| Separate memory | Shared memory possible |
| Heavyweight | Lightweight |
| Good for scaling servers | Good for CPU tasks |
| IPC for communication | Message passing |
Summary:
- Cluster → scaling
- Worker threads → computation
20. What is child_process in Node.js?
child_process is a module used to create and manage separate processes.
Use cases:
- Run system commands
- Execute scripts
- Handle heavy tasks
Methods:
-
exec() -
spawn() -
fork()
Example:
const { exec } = require('child_process');
exec('ls', (err, stdout, stderr) => {
console.log(stdout);
});
21. What is fork in Node.js?
In Node.js, fork() is a method of the child process module used to create a new Node.js process.
Key points:
- Specifically used to run another Node.js script
- Enables communication between parent and child via IPC (Inter-Process Communication)
- Lightweight compared to full process spawning for Node apps
Example:
// parent.js
const { fork } = require('child_process');
const child = fork('child.js');
child.on('message', (msg) => {
console.log("Message from child:", msg);
});
child.send("Hello child");
// child.js
process.on('message', (msg) => {
console.log("Message from parent:", msg);
process.send("Hello parent");
});
22. Difference between spawn() and fork()
| spawn() | fork() |
|---|---|
| Used to run any system command | Used to run Node.js scripts |
| No built-in IPC | Has built-in IPC |
| Returns streams | Returns message-based communication |
| More general | Specialized for Node |
Example:
const { spawn } = require('child_process');
spawn('ls');
const { fork } = require('child_process');
fork('app.js');
23. How does Node.js handle child processes?
Node.js uses the child_process module to create separate processes.
Steps:
- Parent process creates child
- Child runs independently
- Communication via:
- IPC (fork)
- stdin/stdout streams (spawn, exec)
Types:
-
spawn()→ stream-based -
exec()→ buffer-based -
fork()→ Node-specific with IPC
24. Difference between application-level and router-level middleware
Using Express.js
| Application-level middleware | Router-level middleware |
|---|---|
| Applied to entire app | Applied to specific router |
Defined using app.use() | Defined using router.use() |
| Global effect | Scoped effect |
Example:
// Application-level
app.use((req, res, next) => {
console.log("Global middleware");
next();
});
// Router-level
const router = express.Router();
router.use((req, res, next) => {
console.log("Router middleware");
next();
});
25. What is error-handling middleware?
Error-handling middleware is used to handle errors in Express.
Special feature:
- Takes 4 parameters:
(err, req, res, next)
Example:
app.use((err, req, res, next) => {
console.error(err.message);
res.status(500).send("Something went wrong");
});
26. What is next() in Express?
next() is a function used to pass control to the next middleware function in the stack.
Uses:
- Continue execution
- Pass errors →
next(err)
Example:
app.use((req, res, next) => {
console.log("Middleware 1");
next();
});
app.use((req, res) => {
res.send("Response");
});
27. How does Express handle async errors?
Express does not automatically catch async errors (in older patterns).
Solution:
Use try-catch or pass error to next()
Example:
app.get('/', async (req, res, next) => {
try {
throw new Error("Error occurred");
} catch (err) {
next(err);
}
});
Or use wrapper:
const asyncHandler = fn => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
28. What is request–response lifecycle in Express?
The lifecycle defines how a request is processed and response is returned.
Flow:
- Client sends request
- Request enters middleware
- Middleware processes request
- Route handler executes
- Response is sent
- Error middleware (if needed)
29. What is body-parser?
body-parser is middleware used to parse incoming request bodies.
Types:
- JSON parser
- URL-encoded parser
Example:
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.post('/user', (req, res) => {
console.log(req.body);
res.send("Received");
});
Note:
In newer Express versions, it is built-in:
app.use(express.json());
30. How do you handle errors gracefully in Express?
Best practices:
- Use centralized error middleware
- Use try-catch in async code
- Send proper status codes
- Avoid exposing sensitive data
Example:
app.get('/', async (req, res, next) => {
try {
throw new Error("Something failed");
} catch (err) {
next(err);
}
});
app.use((err, req, res, next) => {
res.status(500).json({
message: err.message
});
});
Authentication verifies who the user is. In Node.js, it is commonly implemented using:
Approaches:
- Session-based authentication
- Token-based authentication (JWT)
JWT Example:
const jwt = require('jsonwebtoken');
const token = jwt.sign({ userId: 1 }, 'secret', { expiresIn: '1h' });
// verify
jwt.verify(token, 'secret', (err, decoded) => {
if (err) return console.log("Invalid token");
console.log(decoded);
});
Flow:
- User logs in
- Server validates credentials
- Token/session is created
- User sends token with each request
32. How do you manage sessions in Node.js?
Sessions store user data on the server.
Common tool:
-
express-session
Example:
const session = require('express-session');
app.use(session({
secret: 'secret-key',
resave: false,
saveUninitialized: true
}));
app.get('/', (req, res) => {
req.session.user = "John";
res.send("Session stored");
});
Key points:
- Session ID stored in cookies
- Data stored on server
33. What is Passport.js?
Passport.js is middleware for handling authentication in Node.js.
Features:
- Supports multiple strategies
- Local (username/password)
- OAuth (Google, Facebook)
- Easy integration with Express
34. What is CORS?
CORS (Cross-Origin Resource Sharing) is a security mechanism that controls which domains can access your API.
Problem:
Browsers block requests from different origins by default.
35. How do you handle CORS?
Use middleware like cors.
Example:
const cors = require('cors');
app.use(cors({
origin: 'http://example.com'
}));
You can allow:
- Specific domains
- All domains (
*)
36. What are common security vulnerabilities in Node.js?
- Cross-Site Scripting (XSS)
- Cross-Site Request Forgery (CSRF)
- SQL Injection
- Command Injection
- Insecure dependencies
- Improper authentication
37. How do you secure cookies against XSS?
To protect cookies:
- Use
httpOnly→ prevents JavaScript access - Use
secure→ HTTPS only - Use
sameSite→ restrict cross-site usage
Example:
res.cookie('token', 'value', {
httpOnly: true,
secure: true,
sameSite: 'Strict'
});
38. What is CSRF and how do you prevent it?
CSRF (Cross-Site Request Forgery) is an attack where a user is tricked into performing unwanted actions.
Prevention methods:
- CSRF tokens
- SameSite cookies
- Authentication checks
Example:
Use CSRF middleware:
const csrf = require('csurf');
app.use(csrf());
39. What is rate limiting?
Rate limiting restricts the number of requests a user can make in a given time.
Purpose:
- Prevent abuse
- Avoid DDoS attacks
Example:
const rateLimit = require('express-rate-limit');
app.use(rateLimit({
windowMs: 15 * 60 * 1000,
max: 100
}));
40. What is Helmet and why is it used?
Helmet is middleware that helps secure Express apps by setting HTTP headers.
It protects against:
- XSS
- Clickjacking
- MIME sniffing
Example:
const helmet = require('helmet');
app.use(helmet());
Passwords should never be stored in plain text. They must be hashed using strong algorithms.
Best practices:
- Use hashing algorithms like bcrypt
- Add salt (random data)
- Never store original password
Example:
const bcrypt = require('bcrypt');
const password = "mypassword";
const hashed = await bcrypt.hash(password, 10);
console.log(hashed);
// verify
const isMatch = await bcrypt.compare(password, hashed);
console.log(isMatch);
Key idea:
Hashing is one-way, so original password cannot be recovered.
42. How do you manage secrets and configuration?
Secrets like API keys, DB credentials should not be hardcoded.
Methods:
- Environment variables
-
.envfiles using dotenv - Secret managers (cloud services)
Example:
require('dotenv').config();
console.log(process.env.DB_URL);
Best practices:
- Never commit
.envto Git - Use different configs for dev and production
43. What is a stub?
A stub is a test double that replaces a real function with a controlled implementation.
Purpose:
- Isolate testing
- Return predefined data
Example:
function getData() {
return "real data";
}
// stub replaces this in tests
44. What is mocking?
Mocking simulates the behavior of real objects in testing.
Difference from stub:
- Stub → returns fixed data
- Mock → also tracks interactions
Example:
const mockFn = jest.fn();
mockFn();
expect(mockFn).toHaveBeenCalled();
45. Difference between unit, integration, and end-to-end tests
| Type | Description |
|---|---|
| Unit | Tests individual functions |
| Integration | Tests interaction between modules |
| End-to-End (E2E) | Tests complete application flow |
Example:
- Unit → test a function
- Integration → test API + DB
- E2E → test full login flow
46. What is a test pyramid?
The test pyramid is a strategy for structuring tests.
Structure:
- Many unit tests
- Fewer integration tests
- Very few E2E tests
Reason:
- Unit tests are fast and cheap
- E2E tests are slow and complex
47. What is code coverage?
Code coverage measures how much of your code is tested.
Types:
- Line coverage
- Function coverage
- Branch coverage
Example:
- 80% coverage means 80% code is tested
48. How do you test asynchronous functions?
Use async tools like:
-
async/await - Promises
- Callbacks
Example (Jest):
test("async test", async () => {
const data = await fetchData();
expect(data).toBe("result");
});
49. What tools ensure consistent code style?
Common tools:
- ESLint → linting
- Prettier → formatting
Purpose:
- Maintain consistent code
- Catch errors early
50. Difference between SQL and NoSQL databases
| SQL | NoSQL |
|---|---|
| Relational | Non-relational |
| Structured schema | Flexible schema |
| Tables | Documents / key-value |
| Example: MySQL | Example: MongoDB |
51. How does Node.js connect to databases?
Node.js uses drivers or ORMs to connect to databases.
Examples:
- MongoDB →
mongoose - MySQL →
mysql2
Example:
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test')
.then(() => console.log("Connected"));
52. Can Node.js support JSON-based databases?
Yes, Node.js works well with JSON-based databases like:
- MongoDB
- Firebase
Reason:
JavaScript uses JSON natively, so integration is natural.
53. How does Node.js communicate with external services?
Node.js communicates using:
- HTTP/HTTPS requests
- APIs
- WebSockets
Example:
const axios = require('axios');
axios.get('https://api.example.com')
.then(res => console.log(res.data));