Topics in this subject
JavaScript 2 min read Updated 5 Aug 2026

Modules

CommonJS (Node legacy) 🟡, ES Modules (ESM) 🟢, Dynamic import, ESM vs CommonJS 🎯

CommonJS (Node legacy) 🟡#

// export
const add = (a, b) => a + b;
module.exports = { add };
// import
const { add } = require("./math");

Synchronous, dynamic, runtime resolution — the historic Node system.

ES Modules (ESM) 🟢#

// named exports
export const PI = 3.14159;
export function area(r) { return PI * r * r; }
// default export
export default class Circle {}
// re-export
export { thing } from "./other.js";

// imports
import Circle, { PI, area } from "./circle.js";
import * as circle from "./circle.js";
import "./side-effect.js";           // run for effects only
  • Static (imports are hoisted, resolved before execution) → enables tree shaking (bundlers drop unused exports).
  • Always strict mode; top-level this is undefined.
  • Enable in Node via "type":"module" in package.json or .mjs; in browsers via <script type="module">.

Dynamic import#

const { heavy } = await import("./heavy.js");   // load on demand → code splitting
button.addEventListener("click", async () => {
  const mod = await import("./chart.js");        // lazy-load a feature
  mod.render();
});

Returns a promise; enables lazy loading, conditional loading, and route-based code splitting.

ESM vs CommonJS 🎯#

CommonJS ESM
Syntax require/module.exports import/export
Loading synchronous, runtime static, hoisted
Tree-shakeable no yes
Top-level await no yes
Live bindings copies values live references

Interview Q&A#

Q1. ESM vs CommonJS — three differences. ESM uses import/export with static, hoisted resolution; CommonJS uses require/module.exports resolved synchronously at runtime. ESM is tree-shakeable and supports top-level await, which CommonJS is not/does not. ESM imports are live read-only bindings to the exporter's values, while CommonJS require returns a copy of the exports at import time.

Q2. What is tree shaking and why does ESM enable it? Tree shaking is a bundler eliminating unused exports from the final bundle. ESM's imports/exports are static and analyzable at build time (no conditional require), so a bundler can prove which exports are never used and safely drop them.

Q3. When would you use dynamic import()? When you want to load a module on demand rather than upfront — lazy-loading a heavy feature on user interaction, conditional loading, or route-based code splitting. It returns a promise, so you await it, and it works in both ESM and CommonJS contexts.