| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [View Raw Code] [Original HTTPS Page] |
Scenario-based multiple choice questions covering core Node.js topics.
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.
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.
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.
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.
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.
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.
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.
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).
// 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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).
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.
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.
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.
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', ...).
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.
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.
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.
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.
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.
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.
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.
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.
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().
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.
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.
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.
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.
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.
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.
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.
// 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 | FazBrowse Home | New Git URL |