Skip to the content.

Node.js Scenario-Based MCQ

Scenario-based multiple choice questions covering core Node.js topics.


Table of Contents


# 1. Introduction

Q. A developer wants to run a JavaScript file called server.js from the command line using Node.js. Which command should they use?

Answer: B) node server.js

Node.js executes a script file by passing the filename directly to the node command. npm run is used for executing scripts defined in package.json, not for running arbitrary files.

↥ back to top

Q. A junior developer asks why Node.js is described as “non-blocking.” Which statement best explains this?

Answer: B) Node.js uses an event-driven model where I/O operations do not block the main thread

Node.js is single-threaded but uses an event loop with asynchronous I/O (via libuv) so that while waiting for disk or network operations, the thread is free to process other events. This is what “non-blocking” means in the Node.js context.

↥ back to top

Q. A team is debating whether to use Node.js for a CPU-intensive image processing service. What is the most accurate concern?

Answer: C) Long-running CPU tasks block the event loop, making Node.js a poor fit for CPU-heavy workloads

Because Node.js is single-threaded, a CPU-bound operation (like image compression) occupies the thread the entire time, preventing the event loop from processing any other requests. Worker Threads or separate processes are recommended in such cases.

↥ back to top

Q. What is the output of the following code?

console.log(typeof process);

Answer: C) "object"

process is a global object in Node.js (an instance of EventEmitter) that provides information about, and control over, the current Node.js process. It is not available in browser JavaScript.

↥ back to top

# 2. Event Loop

Q. A developer writes the following code. What is the output order?

console.log('start');

setTimeout(() => console.log('timeout'), 0);

Promise.resolve().then(() => console.log('promise'));

console.log('end');

Answer: B) start, end, promise, timeout

Synchronous code runs first (start, end). Then the microtask queue (Promises) is drained before the macrotask queue (setTimeout). So promise logs before timeout even though the timeout delay is 0.

↥ back to top

Q. Which of the following queues has the highest priority in the Node.js event loop?

Answer: C) process.nextTick callbacks

process.nextTick callbacks are processed at the end of the current operation, before the event loop continues to the next phase — even before Promise microtasks. It has the highest priority of all asynchronous callbacks.

↥ back to top

Q. A developer needs to schedule a callback to run after all I/O events in the current loop iteration but before setTimeout. Which function should they use?

Answer: C) setImmediate()

setImmediate() executes in the check phase of the event loop, which runs after I/O callbacks and before setTimeout/setInterval callbacks (when called within an I/O cycle). It is designed precisely for this use case.

↥ back to top

Q. A developer accidentally calls process.nextTick() recursively inside its own callback. What happens?

function recursive() {
  process.nextTick(recursive);
}
recursive();

Answer: C) The event loop starves — I/O callbacks never execute

process.nextTick callbacks are all drained before the event loop moves to the next phase. Recursively scheduling nextTick keeps the nextTick queue permanently non-empty, preventing I/O and other callbacks from ever running (I/O starvation).

↥ back to top

# 3. Modules

Q. A developer uses CommonJS and wants to expose two functions from a module. Which approach correctly exports them?

// math.js
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }

Answer: B) module.exports = { add, subtract };

In CommonJS, module.exports is the actual exported object. Reassigning exports directly (exports = {}) breaks the reference to module.exports. The correct approach is to assign properties to module.exports.

↥ back to top

Q. A project uses ES Modules ("type": "module" in package.json). A developer tries to use require(). What happens?

Answer: C) A ReferenceError is thrown because require is not defined in ES Modules

require is a CommonJS construct and is not available in ES Module scope. When a file is treated as an ES Module, using require() throws ReferenceError: require is not defined. You must use import instead.

↥ back to top

Q. A developer wants to import only the readFile function from Node.js's built-in fs module using ES Module syntax. Which is correct?

Answer: B) import { readFile } from 'fs/promises';

Named exports from Node.js built-in modules can be imported directly with ES Module destructuring syntax. fs/promises exposes all fs functions as promise-based named exports, making { readFile } import valid and idiomatic.

↥ back to top

Q. What does Node.js do when it cannot find a required module in node_modules?

Answer: B) It looks in parent directories up to the file system root for a node_modules folder

Node.js module resolution traverses up the directory tree, checking each node_modules folder until it finds the module or reaches the root. If not found anywhere, it throws Error: Cannot find module.

↥ back to top

# 4. File System

Q. A developer wants to read a large log file without loading the entire content into memory. Which approach is best?

Answer: C) fs.createReadStream(path)

createReadStream reads the file as a stream, emitting chunks of data instead of loading everything into memory. This is ideal for large files. Both sync and async readFile variants load the entire file into memory before the callback/return.

↥ back to top

Q. A developer writes the following code. What is the issue?

const fs = require('fs');

fs.readFile('./data.json', (err, data) => {
  console.log(data.toString());
});

fs.writeFileSync('./data.json', '{"key":"value"}');

Answer: B) The write may complete before the read, overwriting data being read

readFile is asynchronous while writeFileSync is synchronous. The synchronous write can execute and modify the file before the async read finishes, leading to a race condition. I/O operations on the same file should be properly sequenced.

↥ back to top

Q. A developer needs to watch a file for changes and log a message whenever it is modified. Which method should they use?

Answer: B) fs.watch()

fs.watch() uses the OS kernel's file-watching mechanisms to efficiently detect changes. Polling with setInterval and fs.stat() is resource-intensive and imprecise; fs.readFile in a loop is not designed for file watching at all.

↥ back to top

Q. What does the 'a' flag mean when passed to fs.writeFile()?

Answer: B) Append to the file instead of overwriting it

The 'a' flag opens the file for appending. If the file does not exist, it is created. Using 'w' (the default for writeFile) truncates the file before writing. Use 'a' when you want to add to existing content.

↥ back to top

# 5. HTTP & HTTPS

Q. A developer creates a basic HTTP server. What does the following code print when a browser visits http://localhost:3000?

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello World');
});

server.listen(3000);

Answer: C) Hello World in the browser

The res.end('Hello World') sends the string as the HTTP response body to the client (the browser). Nothing is logged to the terminal because there is no console.log in the code.

↥ back to top

Q. A developer builds a REST API and forgets to call res.end(). What happens?

Answer: B) The client hangs waiting for the response to complete

The HTTP response is only sent to the client when the response is ended (via res.end() or by writing the last chunk with the end flag). Without it, the connection stays open and the client waits indefinitely until a timeout.

↥ back to top

Q. An API server needs to handle CORS. A developer adds the following header. What does it do?

res.setHeader('Access-Control-Allow-Origin', '*');

Answer: B) Allows any origin to make cross-origin requests to this server

The Access-Control-Allow-Origin: * header tells browsers that any website may send cross-origin requests to this server. While convenient for public APIs, this should be restricted to known origins in production for security.

↥ back to top

Q. A developer uses http.get() to consume an external API but only receives partial data. What is the most likely cause?

Answer: B) The response body arrives in chunks and the developer didn't accumulate them before parsing

HTTP responses are streams in Node.js. Data arrives in multiple 'data' events. You must listen for all chunks, concatenate them, then parse in the 'end' event. Parsing on the first 'data' event gives incomplete data.

↥ back to top

# 6. Streams & Buffers

Q. A developer pipes a readable stream to a writable stream. What does pipe() do automatically?

const readable = fs.createReadStream('input.txt');
const writable = fs.createWriteStream('output.txt');
readable.pipe(writable);

Answer: B) Manages backpressure, pausing the readable when the writable cannot keep up

pipe() handles backpressure automatically: it pauses the readable stream when the writable stream's buffer is full, and resumes when the buffer drains. This prevents out-of-memory errors when reading faster than writing.

↥ back to top

Q. A developer needs to create a transform stream that converts all incoming text to uppercase. Which class should they extend?

Answer: C) stream.Transform

A Transform stream is both readable and writable; it receives input, processes it, and outputs transformed data. Readable only produces data, Writable only consumes it, and Duplex is bidirectional without a defined transform relationship.

↥ back to top

Q. What does the following code produce?

const buf = Buffer.from('Node.js', 'utf8');
console.log(buf.toString('hex'));

Answer: C) A hexadecimal representation of the UTF-8 encoded string

Buffer.from('Node.js', 'utf8') creates a buffer from the string using UTF-8 encoding. Calling .toString('hex') converts those bytes to a hex string (e.g., 4e6f64652e6a73). Buffers are Node.js's way of handling raw binary data.

↥ back to top

Q. A developer processes a large CSV upload using streams. They notice memory usage is low and throughput is high. Which stream mode is responsible for this behavior?

Answer: B) Flowing mode activated by attaching a 'data' event listener

Attaching a 'data' event listener switches a Readable stream to flowing mode, where data is emitted as soon as it arrives. This keeps memory low because chunks are processed and discarded rather than accumulated.

↥ back to top

# 7. Events & EventEmitter

Q. A developer emits an event before any listener is registered. What happens?

const { EventEmitter } = require('events');
const emitter = new EventEmitter();

emitter.emit('data', 42);
emitter.on('data', (val) => console.log(val));

Answer: C) The event is lost because no listener existed at the time of emission

EventEmitter does not buffer events. If emit() is called before any listener is attached for that event, the event is silently discarded (except for the special 'error' event, which throws if unhandled).

↥ back to top

Q. A developer registers 15 listeners on a single event. Node.js prints a warning. Why?

Answer: A) EventEmitter has a hard limit of 10 listeners per event by default

Node.js warns when more than 10 listeners are added for a single event on an EventEmitter instance — this is a heuristic to detect potential memory leaks (e.g., listeners added in a loop). The limit can be changed with emitter.setMaxListeners(n).

↥ back to top

Q. A developer wants a listener to fire only the first time an event is emitted and then remove itself. Which method should they use?

Answer: C) emitter.once('event', handler)

emitter.once() registers a one-time listener that is automatically removed after its first invocation. This is useful for events like 'connect' or 'open' that should only trigger setup logic once.

↥ back to top

Q. An unhandled 'error' event is emitted on an EventEmitter. What is the default behavior?

Answer: B) The error is printed to stderr and the process exits with a non-zero code

The 'error' event is special in Node.js. If emitted without a registered 'error' listener, Node.js treats it as an uncaught exception — it prints the error stack trace to stderr and terminates the process.

↥ back to top

# 8. Child Processes

Q. A developer needs to run a shell command from Node.js and capture its stdout. Which method is most appropriate?

Answer: B) child_process.exec()

exec() runs a command in a shell and buffers the entire stdout/stderr output, delivering it to a callback. It is convenient for commands with small output. spawn() is better for large or streaming output. fork() is specifically for spawning Node.js processes.

↥ back to top

Q. A developer spawns a child process using fork() to offload a CPU-intensive task. How do the parent and child processes communicate?

Answer: B) Through a built-in IPC channel using process.send() and process.on('message')

child_process.fork() creates a separate Node.js process and establishes an IPC (Inter-Process Communication) channel. The parent uses child.send(msg) and child.on('message', ...), while the child uses process.send(msg) and process.on('message', ...).

↥ back to top

Q. What is the key difference between exec() and spawn() when running child processes?

Answer: B) exec() buffers all output in memory; spawn() streams output incrementally

exec() collects all stdout/stderr into a buffer and delivers it when the process ends — unsuitable for large output. spawn() returns a child process object with stdout and stderr streams, enabling real-time data processing.

↥ back to top

# 9. npm & Package Management

Q. A developer installs a package with npm install lodash. Where is the package recorded?

Answer: B) In package.json under "dependencies" and in package-lock.json

Since npm v5+, npm install <package> automatically adds the package to "dependencies" in package.json and records the exact resolved version tree in package-lock.json. Use --save-dev to add to "devDependencies" instead.

↥ back to top

Q. A team wants to ensure every developer installs the exact same dependency versions. Which file must be committed to source control?

Answer: C) package-lock.json

package-lock.json records the exact resolved version of every dependency and sub-dependency. Committing it ensures deterministic installs across all environments. node_modules/ should be in .gitignore.

↥ back to top

Q. A developer runs npm ci instead of npm install in a CI pipeline. What is the key difference?

Answer: B) npm ci installs from package-lock.json exactly and deletes node_modules first

npm ci is designed for automated environments. It removes the existing node_modules folder and installs dependencies exactly as specified in package-lock.json. If package.json and package-lock.json are out of sync, it fails — ensuring reproducibility.

↥ back to top

Q. A developer wants to run a script called "test" defined in package.json without specifying the full path to Node. Which command should they use?

Answer: B) npm test or npm run test

npm run <script> executes any script defined in package.json's "scripts" field. For the special scripts test, start, and stop, npm provides shorthand commands (npm test, npm start, npm stop) without the run keyword.

↥ back to top

# 10. Async Programming

Q. What is the issue with the following code?

function fetchData(callback) {
  const data = db.query('SELECT * FROM users');
  callback(null, data);
}

Answer: B) db.query is synchronous here; if it is actually async, the callback is called before data is available

If db.query is asynchronous (as most database calls are), the data variable would be a pending operation rather than the result. The callback should be passed into db.query so it is called when the data is actually ready.

↥ back to top

Q. A developer chains multiple .then() calls but forgets to return the Promise in one of them. What happens?

fetchUser()
  .then(user => {
    fetchProfile(user.id); // forgot to return
  })
  .then(profile => {
    console.log(profile); // what is profile?
  });

Answer: C) profile is undefined because the previous .then returned nothing

When a .then() handler does not explicitly return a value, the Promise resolves with undefined. The next .then() receives undefined as its argument. Always return Promises inside .then() to maintain proper chaining.

↥ back to top

Q. A developer uses Promise.all() with three API calls. One of them rejects. What happens?

Promise.all([api1(), api2(), api3()])
  .then(results => console.log(results))
  .catch(err => console.error(err));

Answer: C) The .catch is called immediately with the first rejection; other promises continue but their results are discarded

Promise.all rejects as soon as any of the input Promises rejects (fail-fast behavior). The remaining Promises continue to run but their results are not passed to .then. Use Promise.allSettled() to wait for all Promises regardless of outcome.

↥ back to top

Q. A developer writes the following async function. What does it return?

async function greet() {
  return 'Hello';
}

console.log(greet());

Answer: B) Promise { 'Hello' }

An async function always returns a Promise. If a non-Promise value is returned, it is wrapped in a resolved Promise. console.log(greet()) logs Promise { 'Hello' }. To get 'Hello', you must await greet() or use .then().

↥ back to top

# 11. Error Handling

Q. A developer uses async/await but does not handle errors. What happens when the awaited Promise rejects?

async function loadData() {
  const data = await fetchFromDB(); // rejects
  return data;
}
loadData();

Answer: B) An UnhandledPromiseRejectionWarning is printed; in Node.js 15+, the process crashes

When a rejected Promise is not caught (no try/catch around await and no .catch() on the returned Promise), it becomes an unhandled rejection. From Node.js v15 onward, unhandled rejections crash the process by default. Always wrap await calls in try/catch.

↥ back to top

Q. A developer defines a custom error class for validation errors. Which is the correct approach?

Answer: B) class ValidationError extends Error { constructor(msg) { super(msg); this.name = 'ValidationError'; } }

Extending the built-in Error class ensures the custom error has a proper stack trace, message, and name. Calling super(msg) initializes the message property. Setting this.name gives a descriptive type for instanceof checks and logging.

↥ back to top

Q. A developer catches an error in an Express route but does not pass it to next(). What is the consequence?

app.get('/data', async (req, res) => {
  try {
    const result = await getData();
    res.json(result);
  } catch (err) {
    console.error(err);
    // forgot: next(err)
  }
});

Answer: B) The request hangs — the client never receives a response

If the catch block only logs the error but neither sends a response (res.json, res.status().send()) nor calls next(err), the request remains open. The client waits indefinitely until a connection timeout.

↥ back to top

Answer: B) Log the error and exit the process gracefully after cleanup

After an uncaughtException, the application is in an unknown, potentially corrupt state. The Node.js documentation strongly recommends performing only minimal cleanup (e.g., logging, flushing buffers) and then exiting. Continuing normal operation risks unpredictable behavior. Use a process manager like PM2 to restart automatically.

↥ back to top

# 12. Express & Middleware

Q. A developer adds middleware with app.use() after the route definitions. What happens when that route is matched?

const express = require('express');
const app = express();

app.get('/hello', (req, res) => res.send('Hi'));

app.use((req, res, next) => {
  console.log('Middleware');
  next();
});

Answer: C) The middleware never runs for /hello requests

Express processes middleware and routes in the order they are defined. Since the /hello route handler sends a response and does not call next(), the request–response cycle ends there. The middleware added after the route never executes for that path.

↥ back to top

Q. A developer registers an Express error-handling middleware. What is the required function signature?

Answer: C) (err, req, res, next) => {}

Express identifies error-handling middleware by its four-parameter signature: (err, req, res, next). If you define middleware with fewer parameters, Express treats it as a regular middleware, not an error handler. All four parameters must be declared even if next is unused.

↥ back to top

Q. A developer wants to parse JSON request bodies in Express. Which middleware should they add?

Answer: C) app.use(express.json())

express.json() is the built-in middleware (available since Express 4.16) that parses incoming requests with JSON payloads and makes the parsed data available on req.body. Without it, req.body is undefined for JSON requests.

↥ back to top

Q. A developer uses Router to organize routes by feature. What is the correct way to mount a router at /api/users?

// userRouter.js
const router = require('express').Router();
router.get('/', getAllUsers);
module.exports = router;

Answer: B) app.use('/api/users', userRouter)

app.use(path, router) mounts the router at the given path prefix. The router's GET / route then handles GET /api/users. Using app.get() would only match that single GET method and would not delegate all HTTP methods to the router.

↥ back to top

# 13. REST APIs

Q. A REST API returns a 201 Created status. What does this indicate?

Answer: B) The request succeeded and a new resource was created

HTTP 201 Created indicates that the server successfully fulfilled the request and created a new resource. It is typically returned in response to POST or PUT requests. The response may include a Location header pointing to the new resource.

↥ back to top

Q. A developer exposes route DELETE /api/users/:id. A client sends a request without an authentication token. Which HTTP status code should the server return?

Answer: B) 401 Unauthorized

401 Unauthorized means the request requires authentication and the client has not provided valid credentials. 403 Forbidden is returned when the client is authenticated but lacks permission. 400 is for malformed requests; 404 when the resource is not found.

↥ back to top

Q. A REST API developer needs to update only the email field of a user without replacing the entire resource. Which HTTP method is appropriate?

Answer: C) PATCH

PATCH applies a partial modification to a resource. PUT replaces the entire resource with the request payload. POST creates a new resource. UPDATE is not a standard HTTP method.

↥ back to top

Q. A developer wants their API to return paginated results. Which query string format follows REST best practices?

Answer: B) /api/products?page=2&limit=10

Query parameters are the standard REST convention for filtering, sorting, and pagination — they do not alter the resource identity. Path segments (like option A or D) are used to identify specific resources, not to control representation. Fragment identifiers (#) are client-side only.

↥ back to top

# 14. Clustering & Worker Threads

Answer: B) Use the cluster module to fork a worker per CPU core

The cluster module allows a Node.js application to create child processes (workers) that share the same server port. The master process distributes incoming connections across workers, making use of multiple CPU cores for I/O-bound workloads.

↥ back to top

Q. A developer wants to run a computationally expensive JSON parsing task without blocking the event loop. Which is the best approach?

Answer: C) Offload it to a Worker Thread

Worker Threads (from the worker_threads module) run JavaScript in a separate thread with its own V8 instance. CPU-intensive tasks can be moved there without blocking the main thread's event loop. setImmediate, Promise, and nextTick all still run on the main thread.

↥ back to top

Q. In a clustered Node.js application, a worker process crashes. What should the master process do?

Answer: B) Listen for the 'exit' event and fork a new worker to replace it

The master process should listen for the 'exit' event on each worker and call cluster.fork() to spawn a replacement. This keeps the application running even if individual workers crash, providing fault tolerance.

↥ back to top

# 15. Environment & Configuration

Q. A developer stores a database password in the source code. Why is this a security problem?

Answer: B) Hard-coded secrets can be exposed if the repository is shared or made public

Hard-coded credentials in source code are a critical security risk (OWASP A02: Cryptographic Failures). They are visible to anyone with repository access, remain in Git history even after removal, and cannot be rotated without a code change. Secrets should be stored in environment variables or a secrets manager.

↥ back to top

Q. A developer uses the dotenv package. What does the following accomplish?

require('dotenv').config();
console.log(process.env.DB_HOST);

Answer: B) Loads variables from a .env file into process.env

dotenv.config() reads a .env file from the project root and parses each KEY=VALUE line into process.env. This allows environment-specific configuration without modifying source code. The .env file itself should be in .gitignore.

↥ back to top

Q. What is NODE_ENV typically used for in a Node.js application?

Answer: C) Indicating the runtime environment (development, test, production) to toggle behavior

NODE_ENV is a convention used by frameworks and libraries (Express, React, etc.) to alter their behavior. For example, Express enables verbose error messages in development and suppresses them in production. Setting NODE_ENV=production often enables performance optimizations.

↥ back to top

# 16. Security

Q. A developer accepts user input and passes it directly to a shell command. What vulnerability does this introduce?

const { exec } = require('child_process');
app.get('/ping', (req, res) => {
  exec(`ping ${req.query.host}`, (err, stdout) => res.send(stdout));
});

Answer: B) Command Injection

Passing unsanitized user input into a shell command allows an attacker to inject arbitrary commands (e.g., host=google.com; rm -rf /). Use execFile() with argument arrays instead of exec() with string interpolation, or validate and whitelist input strictly.

↥ back to top

Q. A developer builds an Express API and wants to protect against common security vulnerabilities. Which package provides a set of HTTP security headers?

Answer: C) helmet

helmet is a middleware that sets various HTTP security headers (e.g., X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security). Adding app.use(helmet()) is a quick win that mitigates many common web vulnerabilities.

↥ back to top

Q. An attacker sends thousands of requests to an API endpoint to overload the server. Which middleware helps mitigate this?

Answer: B) express-rate-limit

express-rate-limit limits the number of requests a client can make in a given time window. Exceeding the limit returns a 429 Too Many Requests response. This mitigates brute-force attacks and basic denial-of-service attempts.

↥ back to top

Q. A developer stores user passwords in the database as plain text. A breach exposes the database. What is the risk and the correct solution?

Answer: B) All passwords are immediately compromised; passwords should be hashed with bcrypt before storage

Plain-text passwords expose all user accounts on breach. bcrypt applies a slow, salted hashing algorithm that is computationally expensive to reverse. AES encryption is reversible (and sharing the key is insecure). Base64 is encoding, not encryption — trivially reversible.

↥ back to top

# 17. Testing

Q. A developer wants to test a function that makes an HTTP call to an external API without hitting the real endpoint. What technique should they use?

Answer: B) Mock the HTTP module or use a library like nock to intercept requests

Mocking isolates the unit under test from external dependencies. nock intercepts Node.js HTTP/HTTPS requests and returns predefined responses, making tests fast, deterministic, and free from network issues or rate limits.

↥ back to top

Q. A developer uses Jest and writes the following test. What does toBe check?

test('sum', () => {
  expect(1 + 1).toBe(2);
});

Answer: B) Strict equality (===) of primitive values

toBe uses Object.is() under the hood, which behaves like strict equality (===). For comparing objects and arrays by value, use toEqual, which performs deep equality. toBe will fail for two different objects even if their contents are identical.

↥ back to top

Q. A developer writes integration tests for Express routes. Which library lets them send HTTP requests to the app without starting a real server?

Answer: B) supertest

supertest wraps an Express app and sends HTTP requests directly to it in-process without binding to a port. It returns a fluent API for asserting status codes, headers, and body content — ideal for route-level integration tests.

↥ back to top

Q. A developer's test suite passes locally but fails in CI because a setTimeout delay is too slow. What is the best fix?

Answer: C) Use Jest's fake timers (jest.useFakeTimers()) to control time in tests

Jest's fake timers replace global timer functions with mocks. You control time with jest.advanceTimersByTime(ms), making time-dependent tests both fast and deterministic. Relying on real timers makes tests flaky and slow.

↥ back to top

# 18. Debugging & Profiling

Q. A developer wants to debug a running Node.js application using Chrome DevTools. Which flag must they start Node.js with?

Answer: B) --inspect

Starting Node.js with --inspect (or --inspect-brk to break on the first line) enables the V8 inspector protocol on ws://127.0.0.1:9229. Chrome DevTools at chrome://inspect can then connect to set breakpoints, inspect variables, and profile CPU/memory.

↥ back to top

Q. A Node.js server's memory usage grows continuously over several hours and eventually crashes. What is the most likely cause?

Answer: B) A memory leak — objects are being retained in memory and never garbage collected

Continuous memory growth is the classic symptom of a memory leak. Common causes include event listeners that are never removed, closures holding large references, or caches that grow without bounds. Tools like Chrome DevTools heap snapshots or clinic.js help identify the leak.

↥ back to top

Q. A developer uses console.time('op') and console.timeEnd('op'). What do these do?

Answer: B) Measure and log the elapsed time between the two calls with the given label

console.time(label) starts a timer with the given label. console.timeEnd(label) stops it and prints the elapsed milliseconds to the console. It is a quick way to measure the duration of synchronous code or async operations.

↥ back to top

# 19. Databases & ORMs

Q. A developer uses Mongoose with MongoDB. They define a schema but query the database before the connection is established. What happens?

Answer: B) Mongoose buffers the operation and executes it once the connection is established

Mongoose has a built-in command buffering mechanism. Queries issued before the connection is open are queued and replayed once mongoose.connect() resolves. This allows developers to define models and queries before connecting without errors.

↥ back to top

Q. A developer uses a raw SQL query with user input. What is the vulnerability and fix?

const query = `SELECT * FROM users WHERE name = '${req.body.name}'`;
db.query(query);

Answer: B) SQL Injection — use parameterized queries or prepared statements instead

String interpolation of user input into SQL queries allows attackers to manipulate the query structure (e.g., '; DROP TABLE users; --). Parameterized queries (e.g., db.query('SELECT * FROM users WHERE name = ?', [req.body.name])) treat input as data, not SQL code.

↥ back to top

Q. A developer fetches a large list of users from a database and returns all fields including passwords and tokens to the API response. What is the issue?

Answer: B) Sensitive fields are unintentionally exposed to clients — over-fetching is a security and performance problem

Always use field projection (SELECT only required columns / Mongoose .select('-password -token')) to exclude sensitive data. Sending raw database records to clients risks leaking credentials and PII, and increases payload size unnecessarily.

↥ back to top

Q. A developer runs a database query inside a loop, resulting in N+1 queries. Which approach resolves this?

const orders = await Order.find();
for (const order of orders) {
  order.user = await User.findById(order.userId); // N queries
}

Answer: B) Use database-level joins or Mongoose's .populate() to fetch related data in fewer queries

The N+1 problem executes one extra query per record. Mongoose .populate('userId') resolves references in a single additional query. SQL ORMs provide JOIN or eager loading (include). Indexing helps query speed but does not reduce query count.

↥ back to top

# 20. Performance & Optimization

Q. A developer notices their Express API has slow response times under load. They profile the app and find JSON serialization of large objects is the bottleneck. What is a practical optimization?

Answer: C) Use JSON.stringify with a replacer to exclude unused fields, or use a faster serialization library like fast-json-stringify

fast-json-stringify uses a compiled schema to serialize JSON significantly faster than the built-in JSON.stringify. Excluding unnecessary fields also reduces payload size, lowering bandwidth and parse time on the client.

↥ back to top

Q. A Node.js API serves the same expensive database query result to hundreds of requests per second. Which strategy best reduces database load?

Answer: B) Cache query results in memory (e.g., with Redis) and serve cached responses for repeated requests

Caching frequent, expensive queries with a TTL in Redis dramatically reduces database hits and response times. The database only receives a query when the cache misses or expires. Connection pool sizing and indexing help but do not eliminate repeated identical queries.

↥ back to top

Q. A developer enables compression middleware in Express. What does it do?

const compression = require('compression');
app.use(compression());

Answer: B) Gzip/Brotli compresses HTTP response bodies, reducing their size for clients that support it

The compression middleware uses zlib to gzip (or deflate) response bodies when the client sends Accept-Encoding: gzip. This reduces the bytes transferred over the network, speeding up page loads and API responses — especially for large JSON payloads.

↥ back to top

Q. A developer wants to avoid restarting Node.js manually every time they change a source file during development. Which tool is most commonly used?

Answer: B) nodemon

nodemon watches source files for changes and automatically restarts the Node.js process. It is the de facto development tool for this use case. pm2 and forever are production process managers. npx is for executing npm packages without installing them globally.

↥ back to top

Q. A production Node.js application crashes and no one is notified. Which tool helps keep the process alive and provides monitoring?

Answer: C) pm2

pm2 is a production process manager for Node.js. It automatically restarts crashed processes, supports clustering, provides real-time monitoring (pm2 monit), log management, and startup scripts. nodemon is for development only.

↥ back to top