QuestionsJavaScript

ES Modules vs CommonJS

ModulesMediumJavaScript

What is the difference between ES Modules and CommonJS, and why does it matter?

What it tests

Understanding the two module systems, static vs dynamic resolution, and live bindings.

Approach & answer

CommonJS (require/module.exports) was Node's original system: it is synchronous and dynamic — require() runs at call time, resolves and executes the module, and returns a COPY of the exported values (a snapshot). Because it's dynamic you can require conditionally, but tools can't statically know the dependency graph. ES Modules (import/export) are the standard, and are static: imports/exports are resolved before execution by parsing, so the dependency graph is known ahead of time. That statically-analyzable structure is what enables tree-shaking (dead-export elimination) and is why import statements must be top-level. ESM exports are LIVE BINDINGS, not copies — if the exporting module reassigns an exported variable, importers see the new value; CJS would not. ESM is asynchronous-friendly and supports top-level await; dynamic import() returns a promise for code-splitting/lazy loading. Interop friction (naming, __dirname, JSON imports, the .mjs/.cjs/`type: module` rules) exists because the systems differ fundamentally. Rule of thumb: author ESM for new code (browser-native, tree-shakeable, future-proof); understand CJS for legacy Node and its require semantics.

Use this technique when

Explaining bundler tree-shaking, code-splitting via dynamic import(), and debugging Node interop / live-binding surprises.

References

js