CommonJS vs ES Modules in Node.js
Understand CommonJS vs ES Modules in Node.js: require vs import, sync vs static loading, interop, top-level await, and how to enable ESM with clear examples.
Expected Interview Answer
CommonJS (CJS) is Node.js's original module system using synchronous require() and module.exports, while ES Modules (ESM) is the JavaScript standard using static import/export that loads asynchronously.
CommonJS resolves and loads modules synchronously at runtime, so require() can be called conditionally anywhere and exports are a mutable object. ES Modules are parsed statically before execution, enabling tree-shaking and top-level await, but imports are hoisted and read-only live bindings. In Node.js you opt into ESM with "type": "module" in package.json or an .mjs extension; CommonJS uses .cjs or the default when type is commonjs.
- ESM enables tree-shaking and smaller bundles
- ESM supports top-level await
- CommonJS allows dynamic conditional require()
- ESM is the cross-platform browser and Node standard
- Clear interop rules via createRequire and dynamic import()
AI Mentor Explanation
CommonJS is like calling for a new bat mid-innings — you send a runner (require) and wait right there until it arrives before facing the next ball, blocking play. ES Modules are like declaring your full kit list to the umpire before the match starts, so everything is checked and laid out ahead of the first delivery and nothing stalls the over.
Step-by-Step Explanation
Step 1
Identify the module system
Check package.json "type" field and the file extension (.mjs = ESM, .cjs = CommonJS).
Step 2
Pick the syntax
Use require()/module.exports for CommonJS and import/export for ES Modules — never mix them in one file.
Step 3
Understand loading
require() loads synchronously at call time; ESM imports are hoisted and resolved statically before execution.
Step 4
Handle interop
In ESM use import() or createRequire() to pull in a CommonJS module; a default import gives you module.exports.
Step 5
Use ESM-only features
Reach for top-level await and static analysis benefits like tree-shaking only in ESM.
What Interviewer Expects
- Knows require/module.exports vs import/export syntax
- Understands synchronous vs static/asynchronous loading
- Can explain how to enable ESM in Node (type field, .mjs)
- Aware of tree-shaking and top-level await in ESM
- Knows interop pitfalls like __dirname absence in ESM
Common Mistakes
- Mixing require and import in the same file
- Forgetting __dirname and __filename do not exist in ESM
- Assuming import can be called conditionally like require
- Not setting "type": "module" and getting syntax errors
- Expecting named imports from a CommonJS module to always work
Best Answer (HR Friendly)
“CommonJS is Node's older way of sharing code using require and module.exports, and it loads pieces one at a time as needed. ES Modules is the newer standard using import and export that plans everything up front, which is faster to optimize and works in browsers too.”
Code Example
// math.cjs
function add(a, b) {
return a + b;
}
module.exports = { add };
// app.cjs
const { add } = require('./math.cjs');
console.log(add(2, 3)); // 5// math.mjs
export function add(a, b) {
return a + b;
}
// app.mjs
import { add } from './math.mjs';
console.log(add(2, 3)); // 5
// top-level await works in ESM
const data = await Promise.resolve(42);Follow-up Questions
- How do you import a CommonJS module from an ES Module?
- Why can't you use __dirname directly in ES Modules?
- What does "type": "module" do in package.json?
- What is top-level await and where is it allowed?
- How does static analysis enable tree-shaking in ESM?
MCQ Practice
1. Which statement about ES Modules in Node.js is true?
ESM imports are statically analyzed and hoisted before execution, which enables tree-shaking and top-level await.
2. How do you enable ES Modules by default in a Node.js package?
Setting "type": "module" makes .js files load as ES Modules; .cjs still loads as CommonJS.
3. Which globals are NOT available in ES Modules?
__dirname and __filename are CommonJS-only; in ESM you derive them from import.meta.url.
Flash Cards
CommonJS export/import syntax? — module.exports to export and require() to import, loaded synchronously.
ES Modules export/import syntax? — export and import statements, resolved statically and loaded asynchronously.
How to enable ESM in Node? — Add "type": "module" to package.json or use the .mjs extension.
ESM-only feature example? — Top-level await and static tree-shaking friendly imports.
Get __dirname in ESM? — Use fileURLToPath(import.meta.url) with path.dirname().