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:

  1. Timers phase
    Executes callbacks scheduled by setTimeout() and setInterval().
  2. Pending Callbacks phase
    Executes I/O callbacks deferred from the previous cycle.
  3. Idle, Prepare phase
    Internal use by Node.js.
  4. Poll phase
    • Retrieves new I/O events
    • Executes I/O callbacks
    • Waits for new events if none exist
  5. Check phase
    Executes callbacks from setImmediate().
  6. Close Callbacks phase
    Executes close events like socket.on('close').

Flow summary:

Timers → Pending → Poll → Check → Close → repeat


2. Difference between process.nextTick() and setImmediate()

process.nextTick()setImmediate()
Executes immediately after current operationExecutes in check phase
Higher priorityLower priority
Runs before event loop continuesRuns 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 phaseRuns in timers phase
Executes after I/O eventsExecutes after delay
More predictable after I/ODepends 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:

  1. process.nextTick queue (highest priority)
  2. Microtask queue (Promises)
  3. 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:

  1. Measure delay using timers
const start = Date.now();

setTimeout(() => {
console.log(Date.now() - start);
}, 100);

If delay is large → event loop is blocked.

  1. Use performance tools
  • clinic.js
  • node --inspect
  • Monitoring tools
  1. 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:

  1. Node.js delegates task to libuv
  2. libuv interacts with OS
  3. OS completes task
  4. 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:

  1. Request comes to Node.js
  2. If I/O operation is needed:
    • Delegated to OS or libuv thread pool
  3. Node.js continues executing other tasks
  4. Once operation completes:
    • Callback is pushed to event queue
  5. 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

ClusterWorker Threads
Uses multiple processesUses multiple threads
Separate memoryShared memory possible
HeavyweightLightweight
Good for scaling serversGood for CPU tasks
IPC for communicationMessage 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 commandUsed to run Node.js scripts
No built-in IPCHas built-in IPC
Returns streamsReturns message-based communication
More generalSpecialized 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:

  1. Parent process creates child
  2. Child runs independently
  3. 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 middlewareRouter-level middleware
Applied to entire appApplied to specific router
Defined using app.use()Defined using router.use()
Global effectScoped 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:

  1. Client sends request
  2. Request enters middleware
  3. Middleware processes request
  4. Route handler executes
  5. Response is sent
  6. 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:

  1. Use centralized error middleware
  2. Use try-catch in async code
  3. Send proper status codes
  4. 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
});
});
31. How do you implement authentication in Node.js?

Authentication verifies who the user is. In Node.js, it is commonly implemented using:

Approaches:

  1. Session-based authentication
  2. 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:

  1. User logs in
  2. Server validates credentials
  3. Token/session is created
  4. 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?

  1. Cross-Site Scripting (XSS)
  2. Cross-Site Request Forgery (CSRF)
  3. SQL Injection
  4. Command Injection
  5. Insecure dependencies
  6. 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());
41. How do you securely store passwords?

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
  • .env files using dotenv
  • Secret managers (cloud services)

Example:

require('dotenv').config();

console.log(process.env.DB_URL);

Best practices:

  • Never commit .env to 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

TypeDescription
UnitTests individual functions
IntegrationTests 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

SQLNoSQL
RelationalNon-relational
Structured schemaFlexible schema
TablesDocuments / key-value
Example: MySQLExample: 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));