pencils ready ✎

Node.js MCQs multiple-choice questions with answers & explanations

All 29 Node.js quiz questions on one page. Pick an answer in your head, then open Show answer to check it and read why. Want a score and a timer? Take them as a quiz instead.

  1. 1.

    In a CommonJS script run with node, what is the output order?

    mid
    setTimeout(() => console.log('timeout'), 0);
    setImmediate(() => console.log('immediate'));
    process.nextTick(() => console.log('nextTick'));
    Promise.resolve().then(() => console.log('promise'));
    console.log('sync');
    1. Async, nextTick, promise, timeout, immediate, always in that order
    2. Bsync, promise, nextTick, timeout, immediate
    3. CnextTick, sync, promise, immediate, timeout
    4. Dsync, nextTick, promise, then timeout and immediate in an order that is not guaranteed
    Show answer

    Answer: D (sync, nextTick, promise, then timeout and immediate in an order that is not guaranteed)

    Synchronous code runs first, then Node drains the process.nextTick queue, then promise microtasks. In the main module, whether the timer (a 0 ms timeout becomes 1 ms) has already expired when the event loop first enters the timers phase depends on process performance, so the Node docs call the timeout/immediate order non-deterministic. Inside an I/O callback, setImmediate always runs first.

  2. 2.

    What is the output order?

    hard
    const fs = require('node:fs');
    
    fs.readFile(__filename, () => {
      setTimeout(() => console.log('timeout'), 0);
      setImmediate(() => console.log('immediate'));
      process.nextTick(() => console.log('nextTick'));
    });
    1. AnextTick, timeout, immediate
    2. BnextTick, immediate, timeout
    3. Cimmediate, nextTick, timeout
    4. DnextTick, then timeout and immediate in a non-deterministic order
    Show answer

    Answer: B (nextTick, immediate, timeout)

    The readFile callback runs in the poll phase. When it returns, the nextTick queue is drained, and the loop then continues to the check phase, where setImmediate callbacks run, before wrapping around to the timers phase on the next iteration. That's why the order is deterministic inside I/O callbacks.

  3. 3.

    In a CommonJS script, what is the output order?

    hard
    Promise.resolve()
      .then(() => {
        console.log('p1');
        process.nextTick(() => console.log('tick inside promise'));
      })
      .then(() => console.log('p2'));
    
    process.nextTick(() => console.log('tick'));
    console.log('sync');
    1. Async, tick, p1, tick inside promise, p2
    2. Bsync, tick, p1, p2, tick inside promise
    3. Csync, p1, p2, tick, tick inside promise
    4. Dsync, p1, tick, tick inside promise, p2
    Show answer

    Answer: B (sync, tick, p1, p2, tick inside promise)

    After the main script, Node drains the nextTick queue (tick), then the whole microtask queue: p1 runs and schedules a new tick, and resolving its promise queues p2, which runs before control returns to the nextTick queue. In an ES module the output differs (sync, p1, p2, tick, tick inside promise) because the module body itself runs inside a promise job, so microtasks drain first.

  4. 4.

    What is logged?

    easy
    const { EventEmitter } = require('node:events');
    
    const bus = new EventEmitter();
    bus.on('order', (id) => console.log('listener', id));
    
    console.log('before');
    bus.emit('order', 42);
    console.log('after');
    1. Abefore, listener 42, after
    2. Bbefore, after, listener 42
    3. Clistener 42, before, after
    4. Dbefore, after (the listener runs on a later tick and its output is lost)
    Show answer

    Answer: A (before, listener 42, after)

    emit() calls every listener synchronously, in registration order, before it returns. EventEmitter isn't asynchronous by itself; if a listener needs to defer work it must use setImmediate, process.nextTick or a promise.

  5. 5.

    What happens when this runs?

    mid
    const { EventEmitter } = require('node:events');
    
    const bus = new EventEmitter();
    bus.emit('error', new Error('boom'));
    console.log('still running');
    1. AIt logs 'still running'; the unhandled event is ignored
    2. BIt prints a warning, then logs 'still running'
    3. CThe error is queued until an error listener is added
    4. Demit throws the error, so the process crashes before 'still running'
    Show answer

    Answer: D (emit throws the error, so the process crashes before 'still running')

    The 'error' event is special: if nothing is listening, emit('error', err) throws err. Any other event without listeners just returns false. Always attach an 'error' listener to emitters and streams you create.

  6. 6.

    What does main.js log?

    easy
    // greet.js
    exports = { hello: 'world' };
    
    // main.js
    const greet = require('./greet');
    console.log(greet);
    1. A{ hello: 'world' }
    2. Bundefined
    3. C{}
    4. DTypeError: exports is read-only
    Show answer

    Answer: C ({})

    exports is just a local variable that starts out pointing at module.exports. Reassigning it breaks that link, and require returns module.exports, which is still the original empty object. Use module.exports = { ... }, or add properties with exports.hello = 'world'.

  7. 7.

    What does running main.js print?

    easy
    // counter.js
    console.log('loading counter');
    let count = 0;
    module.exports = { inc: () => ++count };
    
    // main.js
    const a = require('./counter');
    const b = require('./counter');
    a.inc();
    console.log(b.inc(), a === b);
    1. Aloading counter, loading counter, 1 false
    2. Bloading counter, loading counter, 2 true
    3. Cloading counter, 1 false
    4. Dloading counter, 2 true
    Show answer

    Answer: D (loading counter, 2 true)

    Modules are cached by resolved filename after the first require, so the module body runs once and both calls return the same exports object. The shared count makes a module behave like a singleton. ES modules are cached the same way.

  8. 8.

    What does this print?

    easy
    console.log(Buffer.from('héllo').length, 'héllo'.length);
    1. A5 5
    2. B6 5
    3. C5 6
    4. D10 5
    Show answer

    Answer: B (6 5)

    A string's length counts UTF-16 code units, while Buffer.from encodes as UTF-8 by default, where 'é' takes two bytes. Use Buffer.byteLength(str) when you need a byte count, for example for a Content-Length header.

  9. 9.

    With default settings in current Node versions, what happens when this script runs?

    easy
    Promise.reject(new Error('nobody handles me'));
    
    setTimeout(() => console.log('still alive'), 100);
    1. AA warning is printed, then 'still alive' is logged
    2. BNothing is printed except 'still alive'; the rejection is ignored
    3. CThe process crashes with the error and exit code 1; 'still alive' never prints
    4. DIt crashes only when NODE_ENV=production is set
    Show answer

    Answer: C (The process crashes with the error and exit code 1; 'still alive' never prints)

    By default Node emits 'unhandledRejection', and if no handler is registered it raises the rejection as an uncaught exception, which terminates the process with exit code 1. (Older Node versions only printed a warning.) Handle rejections where they happen, and use a process-level handler only for logging.

  10. 10.

    What does this print?

    mid
    let n = 0;
    setTimeout(() => console.log('timeout sees n =', n), 0);
    
    function spin() {
      if (++n < 100000) process.nextTick(spin);
    }
    spin();
    1. Atimeout sees n = 100000
    2. Btimeout sees n = 1
    3. CA small number that varies between runs
    4. DNothing: the timeout never fires
    Show answer

    Answer: A (timeout sees n = 100000)

    Node drains the entire nextTick queue, including ticks added while draining, before the event loop can move on, so recursive nextTick starves timers and I/O until the recursion stops. With setImmediate(spin) instead, each step waits for the next loop iteration, so the timer fires early with a small, varying n.

  11. 11.

    What is the output order?

    hard
    setImmediate(() => {
      console.log('immediate 1');
      setImmediate(() => console.log('immediate 2'));
      process.nextTick(() => console.log('tick'));
    });
    
    setImmediate(() => console.log('immediate 3'));
    1. Aimmediate 1, immediate 3, tick, immediate 2
    2. Bimmediate 1, immediate 2, tick, immediate 3
    3. Cimmediate 1, tick, immediate 3, immediate 2
    4. Dimmediate 1, tick, immediate 2, immediate 3
    Show answer

    Answer: C (immediate 1, tick, immediate 3, immediate 2)

    Node drains the nextTick and microtask queues after each individual setImmediate callback, so tick runs before immediate 3. An immediate scheduled while the check phase is running is queued for the next loop iteration, so immediate 2 comes last.

  12. 12.

    What is the output order?

    hard
    setTimeout(() => {
      console.log('t1');
      Promise.resolve().then(() => console.log('p1'));
      process.nextTick(() => console.log('tick1'));
    }, 0);
    
    setTimeout(() => console.log('t2'), 0);
    1. At1, t2, tick1, p1
    2. Bt1, tick1, p1, t2
    3. Ct1, p1, tick1, t2
    4. Dt1, t2, p1, tick1
    Show answer

    Answer: B (t1, tick1, p1, t2)

    Node runs the nextTick queue and then the microtask queue after each timer callback (as browsers do), so both run before t2. Within that, process.nextTick callbacks have priority over promise reactions.

  13. 13.

    writable.write(chunk) returns false. What should the code producing the data do?

    mid
    1. ARe-send the chunk, because it was dropped
    2. BStop writing and resume when the stream emits drain
    3. CCall end(), because the stream is full
    4. DKeep writing: false only means the disk is slow
    Show answer

    Answer: B (Stop writing and resume when the stream emits drain)

    false is backpressure: the chunk was accepted and buffered, but the internal buffer has reached highWaterMark. Writing more anyway makes memory grow without bound. Wait for 'drain', or let pipe()/pipeline() handle this for you.

  14. 14.

    Which version satisfies the range ^0.2.3 in package.json?

    mid
    1. A0.3.0
    2. B0.9.0
    3. C1.0.0
    4. D0.2.9
    Show answer

    Answer: D (0.2.9)

    A caret allows changes that don't modify the left-most non-zero part of the version. For 0.x versions the minor number is treated as breaking, so ^0.2.3 means >=0.2.3 <0.3.0. By contrast, ^1.2.3 allows everything below 2.0.0, and ~1.2.3 allows only patch updates below 1.3.0.

  15. 15.

    An HTTP handler calls fs.readFileSync() on a large file for every request. What's the main problem under load?

    easy
    1. AreadFileSync is deprecated and slower than readFile
    2. BIt exhausts the libuv thread pool, so other fs calls queue up
    3. CIt leaks a file descriptor on every call until the process restarts
    4. DIt blocks the event loop, so every other request waits while the file is read
    Show answer

    Answer: D (It blocks the event loop, so every other request waits while the file is read)

    Synchronous fs APIs run on the main thread, so while the read is in progress Node can't run any other JavaScript or callbacks. Use fs.promises.readFile, or better, stream it with fs.createReadStream(path).pipe(res) so memory stays flat too. Sync APIs are fine at startup, e.g. for reading config.

  16. 16.

    A CPU-heavy image hash inside an Express route makes every request slow. What's the best fix within the same Node process?

    mid
    1. AWrap the hashing in new Promise(...) so it runs asynchronously
    2. BIncrease UV_THREADPOOL_SIZE
    3. CRun it in a pool of worker_threads
    4. DSchedule it with process.nextTick
    Show answer

    Answer: C (Run it in a pool of worker_threads)

    A Promise executor runs synchronously on the main thread, and nextTick only postpones the same blocking work. The libuv thread pool size affects Node's own async operations (fs, dns.lookup, crypto, zlib), not your JavaScript. Worker threads run JS on separate threads with their own event loops; reuse them through a pool (e.g. Piscina) rather than spawning one per request.

  17. 17.

    What happens when you run node app.mjs?

    easy
    // app.mjs
    console.log(__dirname);
    1. AReferenceError: __dirname is not defined in ES module scope
    2. BIt prints the directory containing app.mjs
    3. CIt prints undefined
    4. DIt prints the current working directory
    Show answer

    Answer: A (ReferenceError: __dirname is not defined in ES module scope)

    __dirname, __filename, require, module and exports are injected by the CommonJS module wrapper and don't exist in ES modules. Use import.meta.dirname / import.meta.filename in recent Node versions, or derive them with fileURLToPath(import.meta.url).

  18. 18.

    What happens when you run node script.cjs?

    easy
    // script.cjs
    const data = await Promise.resolve(42);
    console.log(data);
    1. AIt prints 42
    2. BIt prints a pending Promise, because CommonJS ignores await
    3. CIt throws a SyntaxError: top-level await is not allowed in CommonJS
    4. DIt prints undefined, because the module finishes before the promise
    Show answer

    Answer: C (It throws a SyntaxError: top-level await is not allowed in CommonJS)

    Top-level await is only allowed in ES modules. Use a .mjs file or "type": "module" in package.json, or wrap the code in an async function.

  19. 19.

    Both files are CommonJS. What does node a.js print?

    hard
    // a.js
    exports.loaded = false;
    const b = require('./b');
    exports.loaded = true;
    console.log('a sees b.aWasLoaded =', b.aWasLoaded);
    
    // b.js
    const a = require('./a');
    exports.aWasLoaded = a.loaded;
    1. Aa sees b.aWasLoaded = true
    2. Ba sees b.aWasLoaded = false
    3. Ca sees b.aWasLoaded = undefined
    4. DRangeError: Maximum call stack size exceeded
    Show answer

    Answer: B (a sees b.aWasLoaded = false)

    When b.js requires a.js while a.js is still executing, Node returns a's partially filled module.exports from the cache instead of loading it again, so b sees loaded: false. Circular requires don't loop forever, but they expose incomplete exports; restructure the modules or read the property later, at call time.

  20. 20.

    On an 8-core machine with default settings, a single one of these hashes takes about T ms. Roughly when do the six callbacks fire?

    hard
    const crypto = require('node:crypto');
    const start = Date.now();
    
    for (let i = 1; i <= 6; i++) {
      crypto.pbkdf2('secret', 'salt', 300000, 64, 'sha512', () => {
        console.log(i, Date.now() - start, 'ms');
      });
    }
    1. AAll six at about T
    2. BOne after another, about T apart (T, 2T, ... 6T)
    3. CFour at about T, the other two at about 2T
    4. DAll six at about 6T
    Show answer

    Answer: C (Four at about T, the other two at about 2T)

    Async crypto.pbkdf2 runs on libuv's thread pool, which has 4 threads by default, so four hashes run in parallel and the last two wait for a free thread. The main thread stays free throughout. UV_THREADPOOL_SIZE, set before the pool is first used, changes the size.

  21. 21.

    What happens when this runs?

    mid
    const { EventEmitter } = require('node:events');
    
    const e = new EventEmitter();
    for (let i = 0; i < 11; i++) e.on('data', () => {});
    e.emit('data');
    console.log('done');
    1. AAll 11 run and 'done' is logged, plus a MaxListenersExceededWarning
    2. BIt throws a RangeError when the 11th listener is added
    3. CThe 11th listener is silently dropped, so only 10 run
    4. DOnly the first 10 listeners run, and emit returns false
    Show answer

    Answer: A (All 11 run and 'done' is logged, plus a MaxListenersExceededWarning)

    The default limit of 10 listeners per event is a leak detector, not a hard limit: Node only warns about a possible memory leak, typically caused by adding a listener per request and never removing it. Fix the leak, or raise the limit with setMaxListeners() if many listeners are intentional.

  22. 22.

    You need to run the convert CLI on a user-supplied filename. Which call avoids shell command injection?

    mid
    1. Aexec('convert ' + file + ' out.png')
    2. BexecFile('convert', [file, 'out.png'])
    3. Cexec('convert "' + file + '" out.png')
    4. Dspawn('convert ' + file, { shell: true })
    Show answer

    Answer: B (execFile('convert', [file, 'out.png']))

    exec and shell: true pass the whole string to a shell, so a filename like x; rm -rf ~ becomes a second command, and adding quotes is easy to break out of. execFile (or spawn without a shell) passes arguments straight to the program. Still validate the input, e.g. a value starting with - could be read as an option.

  23. 23.

    Why prefer stream.pipeline(src, transform, dest, callback) over src.pipe(transform).pipe(dest)?

    mid
    1. Apipe() ignores backpressure, so fast sources overflow memory
    2. Bpipeline() runs each stream in its own worker thread
    3. Cpipe() only works with file streams, not sockets or transforms
    4. Dpipeline() sends any stream's error to one callback and destroys all the streams
    Show answer

    Answer: D (pipeline() sends any stream's error to one callback and destroys all the streams)

    .pipe() does handle backpressure, but it doesn't propagate errors: if one stream fails, the others stay open (leaking file descriptors or sockets) unless you add error handlers to each. pipeline wires up errors and cleanup for the whole chain; a promise version is available from node:stream/promises.

  24. 24.

    What's the vulnerability, and what's the fix?

    mid
    const root = '/srv/app/public';
    
    app.get('/files', (req, res) => {
      const filePath = path.join(root, req.query.name);
      res.sendFile(filePath);
    });
    
    // GET /files?name=../../../etc/passwd
    1. ANone: path.join strips .. segments from its arguments
    2. BXSS: the filename must be HTML-escaped before joining
    3. CPath traversal: resolve the full path and reject it unless it stays inside root
    4. DCSRF: the request needs an anti-CSRF token
    Show answer

    Answer: C (Path traversal: resolve the full path and reject it unless it stays inside root)

    path.join normalizes .. segments, which walks up and out of root, here to /etc/passwd. Resolve the path with path.resolve(root, name) and check that it starts with root + path.sep, or better, map an allow-listed file id to a path instead of accepting paths from users.

  25. 25.

    In a CI pipeline, why use npm ci instead of npm install?

    easy
    1. AIt installs exactly what the lockfile specifies, and fails if it is out of sync
    2. BIt is faster because it skips the lockfile and resolves versions fresh
    3. CIt upgrades every dependency to the newest version its range allows
    4. DIt installs only devDependencies, which is all CI needs
    Show answer

    Answer: A (It installs exactly what the lockfile specifies, and fails if it is out of sync)

    npm ci gives reproducible installs: it deletes any existing node_modules, installs from the lockfile, and errors out on a mismatch. npm install may resolve newer versions within your semver ranges and update the lockfile.

  26. 26.

    You publish a React component library. How should its package.json declare react?

    mid
    1. AIn dependencies, so it installs automatically for every consumer
    2. BIn devDependencies only, since React is only needed at build time
    3. CIn peerDependencies (and devDependencies), so the app supplies React
    4. DIn bundleDependencies, so the library ships its own React copy
    Show answer

    Answer: C (In peerDependencies (and devDependencies), so the app supplies React)

    A regular dependency could leave the app with two copies of React, which breaks hooks and context. A peer dependency says "I need the host app's react, within this range"; modern npm installs peers automatically and reports conflicts.

  27. 27.

    What should a process.on('uncaughtException', handler) handler do in a production server?

    mid
    1. ALog the error and keep serving requests as normal
    2. BRetry the operation that threw
    3. CNothing else: registering it is enough to make the process safe
    4. DLog it, do synchronous cleanup, then exit and let a process manager restart the service
    Show answer

    Answer: D (Log it, do synchronous cleanup, then exit and let a process manager restart the service)

    After an uncaught exception the process is in an unknown state: half-finished operations, leaked resources, broken invariants. The Node docs recommend using the event only for synchronous cleanup before exiting. Let a supervisor such as systemd, Kubernetes or PM2 restart the process.

  28. 28.

    What is the difference between Buffer.alloc(size) and Buffer.allocUnsafe(size)?

    mid
    1. AallocUnsafe is deprecated and will be removed
    2. Balloc zero-fills the memory; allocUnsafe doesn't, so it may hold old data
    3. Calloc creates a fixed-size buffer and allocUnsafe a growable one
    4. DallocUnsafe is not bounds-checked, so writes past the end corrupt memory
    Show answer

    Answer: B (alloc zero-fills the memory; allocUnsafe doesn't, so it may hold old data)

    allocUnsafe returns uninitialized memory (possibly from a shared internal pool), which can hold leftover data from earlier allocations, including secrets. Only use it when you'll immediately overwrite every byte; otherwise use Buffer.alloc.

  29. 29.

    What does the cluster module give you?

    mid
    1. AMultiple threads that share one V8 heap and event loop
    2. BSeveral worker processes sharing one server port, each with its own memory
    3. CAutomatic load balancing of requests across several machines
    4. DA shared in-memory cache that all workers read and write
    Show answer

    Answer: B (Several worker processes sharing one server port, each with its own memory)

    cluster forks worker processes (with child_process.fork) and distributes incoming connections among them so one server can use every CPU core. Workers don't share memory, so sessions and caches must live in an external store such as Redis. In containers, one process per container scaled horizontally is often preferred.

esc