Node.js Interview Mock sample questions with answers
10 questions from the Node.js Interview Mock practice bank, spread across its domains. Pick your answer, then open the explanation to see why each option is right or wrong.
- Question 1Async Programming and I/O
In
pipeline(source, transform, dest, callback), the transform calls its callback with an error. Which three things happen? (Choose three.)Choose 3.
- A
sourceis destroyed, which also closes its file descriptor - B
destis destroyed even though it never failed itself - C
The pipeline callback is called once, with that error
- D
The error is also thrown as an uncaught exception
- E
The source keeps reading so that its buffered data is not lost
Show the answer and explanation
Answer: A and B and C
This is the main reason to prefer
pipelineover chained.pipe()calls. With.pipe()each stream needs its ownerrorhandler, a failing stream does not tear down the others, and an unhandlederrorevent crashes the process.Why the other options are wrong
D. pipeline attaches error handlers, so nothing goes uncaught.
E. The source is destroyed, not paused or drained.
- A
- Question 2Building Production Services
In Node.js 20 and later, outbound requests made with
http.requestthrough the defaulthttp.globalAgentreuse keep-alive connections by default.- A
True
- B
False
Show the answer and explanation
Answer: A
Node 19 changed
http.globalAgentandhttps.globalAgentto keep connections alive, withmaxFreeSocketsof 256 and nomaxSocketslimit. This speeds up service-to-service calls, but it also exposes the keep-alive race: the server may close an idle socket just as the client reuses it, which shows up as occasionalECONNRESET.Why the other options are wrong
B. This was true before Node 19, when every request opened and closed its own connection.
- A
- Question 3Runtime Internals
What does this CommonJS script log?
const { AsyncLocalStorage } = require('async_hooks'); const als = new AsyncLocalStorage(); als.run({ id: 1 }, () => { setTimeout(() => console.log('timeout', als.getStore()?.id), 0); setImmediate(() => console.log('immediate', als.getStore()?.id)); }); setTimeout(() => console.log('outside', als.getStore()), 0);- A
timeout undefined,immediate undefined,outside undefined - B
timeout 1,immediate 1,outside undefined - C
timeout 1,immediate 1andoutside 1 - D
Only
timeout 1, because immediates don't carry async context
Show the answer and explanation
Answer: B
AsyncLocalStorage records the active store when an async resource such as a timer or immediate is created, and restores it when the callback runs. Both callbacks created inside
runsee{ id: 1 }. The outer timer was created outside, so it seesundefined. Logging order between the main-module timer and immediate isn't guaranteed, but the stored values are. Verified on Node 24.Why the other options are wrong
A. The store is captured when each callback is scheduled inside
run.C.
runonly sets the store for code inside its callback.D. Immediates propagate context just like timers.
- A
- Question 4Modules and Tooling
A transpiled CommonJS file sets
exports.__esModule = true; exports.default = 9; exports.z = 1;. What doesimport x from './compiled.cjs'give an ES module on Node 24?- A
9, because Node honours the__esModuleflag and usesexports.default - B
The whole
module.exportsobject, so the value 9 is atx.default - C
undefined, because a__esModulemodule has no default export - D
It throws, because
__esModulemodules must be loaded withrequire
Show the answer and explanation
Answer: B
Bundlers and TypeScript's
esModuleInteroptreat__esModule: trueas "this was ESM, so useexports.default". Node deliberately does not: the default import of CommonJS ismodule.exports, givingx.default === 9. This mismatch is why code that works in a bundler can break when run directly in Node, and why packages should ship real ESM or use"exports"conditions.Why the other options are wrong
A. Node's ESM loader does not apply Babel-style default interop.
C. Every CommonJS module imported into ESM has a default export:
module.exports.D. Importing such files works; only the default binding differs from what bundlers do.
- A
- Question 5Async Programming and I/O
This guard is meant to stop path traversal. Which input gets past it?
const base = '/srv/uploads'; const target = path.resolve(base, userPath); if (!target.startsWith(base)) throw new Error('forbidden'); return fs.createReadStream(target);- A
../../etc/passwd - B
/etc/passwd - C
../uploads-old/secret.txt - D
photo.jpg/../../../etc/passwd
Show the answer and explanation
Answer: C
A string prefix check does not respect path boundaries:
/srv/uploads-oldstarts with/srv/uploads. A robust check computesconst rel = path.relative(base, target)and rejects it ifrel.startsWith('..')orpath.isAbsolute(rel), or compares againstbase + path.sep. Also consider symlinks inside the folder, whichresolvedoes not follow; usefs.realpathwhen that matters.Why the other options are wrong
A. That resolves to /etc/passwd, which fails the prefix check.
B. resolve returns /etc/passwd, which fails the prefix check.
D. That still normalizes to /etc/passwd and is rejected.
- A
- Question 6Building Production Services
An Express API using MongoDB has this login check. An attacker sends the JSON body
{"user":"admin","password":{"$ne":null}}. What happens, and what is the fix?app.post('/login', express.json(), async (req, res) => { const u = await users.findOne({ user: req.body.user, password: req.body.password }); if (u) return res.send('welcome'); res.status(401).end(); });- A
Nothing: MongoDB treats the object as a literal value and finds no match
- B
It logs in as admin; require both fields to be strings
- C
It throws a cast error that the server reports as a 500
- D
It logs in as admin; switching to
findOnewith.lean()stops it
Show the answer and explanation
Answer: B
This is NoSQL operator injection.
express.json()happily produces nested objects, and MongoDB interprets keys starting with$as operators. Validate request bodies with a schema (zod, Joi, JSON Schema) that requires strings, or cast withString(...), or use Mongoose withsanitizeFilter. Of course, real code must also compare password hashes rather than plain text.Why the other options are wrong
A. Objects in a query filter are parsed as operators, so
{ $ne: null }is a condition.C. Plain drivers accept operator objects; there is no cast step.
D.
.lean()only changes the return type, not how the filter is interpreted.
- A
- Question 7Runtime Internals
Since Node 11, if one timer callback schedules a
process.nextTickand a promise callback, both run before the next timer callback that is due in the same timers phase.- A
True
- B
False
Show the answer and explanation
Answer: A
Node 11 aligned with browsers: microtasks (and nextTick) are drained after each
setTimeout,setIntervalandsetImmediatecallback, not only once per phase. So with two zero-delay timers, where the first schedules a tick and a promise, the output ist1 n1 p1 t2. Verified on Node 24.Why the other options are wrong
B. Before Node 11 that was true only between phases; now it happens between each callback.
- A
- Question 8Modules and Tooling
On Node.js 22.12, CommonJS code runs
require('./setup.mjs'), wheresetup.mjsstarts withconst cfg = await loadConfig();. What happens?- A
The
requirecall blocks until the awaited promise settles, then returns the module - B
It throws ERR_REQUIRE_ASYNC_MODULE, because the module uses top-level await
- C
It returns a promise for the module namespace, like dynamic
import() - D
It throws ERR_REQUIRE_ESM, because CommonJS can never load ES modules
Show the answer and explanation
Answer: B
Node 22.12+ and 20.19+ let
require()load ES modules, butrequiremust return synchronously. If the module, or anything it imports, uses top-levelawait, Node throws ERR_REQUIRE_ASYNC_MODULE. The options are to useawait import()from an async function, or to restructure the module to export aninit()function instead of awaiting at load time.Why the other options are wrong
A.
requireis synchronous and cannot wait on a promise.C. Only
import()returns a promise;requirethrows instead.D. Since 22.12,
requirecan load ES modules; the problem here is the top-level await.
- A
- Question 9Async Programming and I/O
This loader caches results. What does the script log?
const cache = new Map(); function load(key, cb) { if (cache.has(key)) return cb(null, cache.get(key)); setTimeout(() => { cache.set(key, 42); cb(null, 42); }, 5); } function test(label) { let x = 'before'; load('k', () => console.log(label, x)); x = 'after'; } test('first'); setTimeout(() => test('second'), 20);- A
first after,second after - B
first before,second before - C
first after,second before - D
first before,second after
Show the answer and explanation
Answer: C
This is "releasing Zalgo": an API that sometimes calls back synchronously and sometimes asynchronously. Callers cannot reason about ordering, and code after the call may or may not have run. Make it consistently async, for example
process.nextTick(cb, null, cache.get(key))on the cache-hit path.Why the other options are wrong
A. On a cache hit the callback runs synchronously, before
xis reassigned.B. On the first call the callback is asynchronous, so
xis alreadyafter.D. That is the reverse of what a miss and a hit do here.
- A
- Question 10Building Production Services
An Express app renders profiles with EJS, using
<p><%- user.bio %></p>. A user saves a bio containing<script>…</script>, and it runs in other visitors' browsers. What is the fix?- A
Switch to
res.send()instead ofres.render(), which escapes HTML - B
Use
<%= user.bio %>, which HTML-escapes the value before output - C
Strip
<script>tags from the bio with a regular expression before saving - D
Set
app.set('view cache', true)so templates are compiled safely
Show the answer and explanation
Answer: B
EJS has two output tags:
<%= value %>escapes HTML special characters, and<%- value %>inserts raw HTML for trusted markup only. Rendering user content with<%-is stored XSS. Other engines work the same way: Pug uses#{}(escaped) and!{}(raw), Handlebars{{}}and{{{}}}. Escape on output, and add a Content Security Policy as defence in depth.Why the other options are wrong
A.
res.sendsends the string as is; it escapes nothing.C. Regex filtering misses event handlers and other payloads; escape on output instead.
D. View caching only affects performance, not escaping.
- A
Practise all 477 NODE questions
Start with the free 15-question diagnostic. It shows where to focus, and your results carry over if you sign up.