Interview frequency: …
Short answer
CJS returns an incomplete exports object while modules load synchronously, whereas ESM links live bindings, but reading an uninitialized variable can throw a ReferenceError.
Explanation
A circular dependency occurs when file A depends on B while B also depends on A. It is usually better to refactor the code and move the shared part into a separate third file.
Code example
// a.js (CommonJS) — b.js requires a.js back, so b receives a PARTIAL exports object
exports.loaded = false;
const b = require("./b"); // while b runs, require("./a") returns { loaded: false }
exports.loaded = true;
// Fix: move the shared code into a third module that neither a nor b imports back
// shared.js
module.exports = { formatPrice: (n) => n.toFixed(2) };
// a.js and b.js: const { formatPrice } = require("./shared");
// ESM: bindings are live, but reading one before its module has evaluated throws a ReferenceError (TDZ)Common mistakes
- Relying on a random import order to hide circular dependency errors.
Follow-up questions
- Compare CommonJS (CJS) and ES Modules (ESM) in Node.js. How does the runtime handle them?
- What is the Dual Package Hazard when publishing a library that supports both CJS and ESM in Node.js?