Menu
CoddyTech

Node.js Modules Interview Questions

How require and import find, load and cache code, and where the two systems differ.

10 questions
  1. What is the difference between CommonJS and ES modules in Node.js?

    CommonJS (require) is Node's original synchronous system; ES modules (import) are the standard, linked before the code runs.

    CommonJSES modules
    Top-level awaitnoyes
    __dirnameyesno, import.meta.dirname
    Extension in relative pathsoptionalrequired
    Imported valuesread once at requirelive bindings

    Since Node 22.12 (and 20.19), CommonJS can require() an ES module without top-level await. See CommonJS vs ES modules.

  2. What happens when you reassign exports instead of module.exports?

    JavaScript
    exports.a = 1;
    exports = { b: 2 };
    module.exports.c = 3;
    console.log(JSON.stringify(module.exports));

    exports starts as a reference to module.exports, which is what require returns. exports.a = 1 adds to that object; exports = { b: 2 } only rebinds the local variable. To export one function or class, assign module.exports.

  3. How does require work, and where do exports, require and __dirname come from?

    Node wraps each CommonJS file in a function called with exports, require, module, __filename and __dirname, so those names are local to each file.

    JavaScript
    // a CommonJS file: these names come from the module wrapper
    console.log(arguments.length);
    console.log(typeof exports, typeof require, typeof module);
    console.log(typeof __filename, typeof __dirname);
    console.log(require.main === module);

    require resolves the path, runs the file once, caches module.exports and returns it. require.main === module means the file was run directly.

  4. Are Node.js modules cached? What does that mean for singletons?

    Yes. The first require runs the file and caches it by resolved path; later calls return the same module.exports. So an exported database pool is a singleton per process.

    // counter.js
    let count = 0;
    module.exports = { next: () => ++count };
    
    // app.js
    const a = require('./counter');
    const b = require('./counter');
    console.log(a === b, a.next(), b.next()); // true 1 2
    

    Traps: one package installed in two node_modules folders gives two instances, and each cluster worker or worker thread has its own cache.

  5. How does Node.js resolve the path in require('something')?

    In this order:

    1. Core modules: fs or node:fs (node:test exists only with the prefix).
    2. A path starting with ./, ../ or /: the exact name, then .js, .json, .node, then a folder's main or index.js.
    3. A package: node_modules in this folder, then in each parent up to the root, then the package's exports field (or main).

    ES modules do not try extensions, and exports blocks paths it does not list.

  6. What happens with circular dependencies in Node.js?

    Node hands out a partly filled module. When a.js and b.js require each other, b gets a's module.exports as it is at that moment.

    // a.js
    exports.loaded = false;
    const b = require('./b');
    console.log('in a, b.loaded =', b.loaded);
    exports.loaded = true;
    
    // b.js
    const a = require('./a');
    console.log('in b, a.loaded =', a.loaded);
    exports.loaded = true;
    
    // node a.js prints:
    // in b, a.loaded = false
    // in a, b.loaded = true
    

    In ES modules, reading an imported let, const or class too early throws a ReferenceError. Fix the design: move the shared code into a third module, or require lazily inside a function.

  7. What is dynamic import() and when do you use it in Node.js?

    import() loads a module at runtime and returns a promise for it, in CommonJS and ES modules, with any expression as the specifier.

    JavaScript
    async function load(name) {
      // the module is chosen at runtime, which a static import cannot do
      return import(name);
    }
    
    load('node:path').then((path) => {
      console.log(path.join('reports', '2026', 'march.csv'));
      console.log(path.extname('archive.tar.gz'));
    });

    Use it to load an ES-only package from CommonJS, plugins named in config, or a heavy module only when needed.

  8. How does Node.js decide whether a file is CommonJS or an ES module?

    .mjs is always an ES module, .cjs always CommonJS. A .js file follows the nearest package.json "type": "module" means ESM, "commonjs" CommonJS. With no type field (or no package.json), Node 22.7 and later try CommonJS first and rerun a file that uses import or export as ESM (syntax detection); that costs a second parse and warns when a package.json exists, so set "type" explicitly. See ES modules.

  9. What are core modules in Node.js, and what does the node: prefix mean?

    Core modules ship with Node.js itself: fs, path, http, crypto, events, stream, os and more, loaded with no npm install. The node: prefix says plainly that you mean the built-in, never a package with the same name.

    JavaScript
    const path = require('node:path');
    const util = require('node:util');
    const { createHash } = require('node:crypto');
    
    console.log(path.basename('/var/log/app/error.log', '.log'));
    console.log(util.format('%s has %d items', 'cart', 3));
    console.log(createHash('sha256').update('coddy').digest('hex').slice(0, 16));
    console.log(require('fs') === require('node:fs'));

    Both names return the same cached module. Newer built-ins such as node:test and node:sqlite exist only with the prefix.

  10. What does the exports field in package.json do?

    exports lists a package's public entry points. It takes priority over main, can serve ES modules and CommonJS from one package through conditions, and blocks every path it does not list.

    {
      "name": "my-lib",
      "exports": {
        ".": {
          "import": "./dist/index.mjs",
          "require": "./dist/index.cjs"
        },
        "./utils": "./dist/utils.mjs"
      }
    }
    

    Now require('my-lib/dist/internal.js') fails with ERR_PACKAGE_PATH_NOT_EXPORTED. Conditions are checked in order, so put default last. Adding exports to a published package is a breaking change.

Coddy programming languages illustration

Learn to code with Coddy

Get started