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.

  1. 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

      source is destroyed, which also closes its file descriptor

    • B

      dest is 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 pipeline over chained .pipe() calls. With .pipe() each stream needs its own error handler, a failing stream does not tear down the others, and an unhandled error event 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.

  2. Question 2Building Production Services

    In Node.js 20 and later, outbound requests made with http.request through the default http.globalAgent reuse keep-alive connections by default.

    • A

      True

    • B

      False

    Show the answer and explanation

    Answer: A

    Node 19 changed http.globalAgent and https.globalAgent to keep connections alive, with maxFreeSockets of 256 and no maxSockets limit. 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 occasional ECONNRESET.

    Why the other options are wrong

    • B. This was true before Node 19, when every request opened and closed its own connection.

  3. 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 1 and outside 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 run see { id: 1 }. The outer timer was created outside, so it sees undefined. 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. run only sets the store for code inside its callback.

    • D. Immediates propagate context just like timers.

  4. Question 4Modules and Tooling

    A transpiled CommonJS file sets exports.__esModule = true; exports.default = 9; exports.z = 1;. What does import x from './compiled.cjs' give an ES module on Node 24?

    • A

      9, because Node honours the __esModule flag and uses exports.default

    • B

      The whole module.exports object, so the value 9 is at x.default

    • C

      undefined, because a __esModule module has no default export

    • D

      It throws, because __esModule modules must be loaded with require

    Show the answer and explanation

    Answer: B

    Bundlers and TypeScript's esModuleInterop treat __esModule: true as "this was ESM, so use exports.default". Node deliberately does not: the default import of CommonJS is module.exports, giving x.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.

  5. 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-old starts with /srv/uploads. A robust check computes const rel = path.relative(base, target) and rejects it if rel.startsWith('..') or path.isAbsolute(rel), or compares against base + path.sep. Also consider symlinks inside the folder, which resolve does not follow; use fs.realpath when 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.

  6. 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 findOne with .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 with String(...), or use Mongoose with sanitizeFilter. 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.

  7. Question 7Runtime Internals

    Since Node 11, if one timer callback schedules a process.nextTick and 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, setInterval and setImmediate callback, not only once per phase. So with two zero-delay timers, where the first schedules a tick and a promise, the output is t1 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.

  8. Question 8Modules and Tooling

    On Node.js 22.12, CommonJS code runs require('./setup.mjs'), where setup.mjs starts with const cfg = await loadConfig();. What happens?

    • A

      The require call 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, but require must return synchronously. If the module, or anything it imports, uses top-level await, Node throws ERR_REQUIRE_ASYNC_MODULE. The options are to use await import() from an async function, or to restructure the module to export an init() function instead of awaiting at load time.

    Why the other options are wrong

    • A. require is synchronous and cannot wait on a promise.

    • C. Only import() returns a promise; require throws instead.

    • D. Since 22.12, require can load ES modules; the problem here is the top-level await.

  9. 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 x is reassigned.

    • B. On the first call the callback is asynchronous, so x is already after.

    • D. That is the reverse of what a miss and a hit do here.

  10. 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 of res.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.send sends 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.

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.

Go to NODE