JavaScript Notes
Master JavaScript modules: ES modules, import export syntax, default vs named exports, dynamic import, CommonJS vs ESM, bundlers, and top interview Q&A.
What are JavaScript Modules?
Modules are reusable pieces of JavaScript code that can export values (functions, objects, variables) and import values from other modules. Each module has its own scope — variables defined inside are not visible outside unless explicitly exported.
Before modules, all JavaScript shared a single global scope — libraries would conflict by using the same variable names. Modules solve this with clear, explicit boundaries.
One-liner: Modules let you split JavaScript into separate files with their own scope, explicitly sharing what's needed viaexportandimport.
Module System Evolution
Named Exports and Imports
Exporting
Importing
// main.js
import { PI, add, multiply } from './math.js';
console.log(PI);
console.log(add(3, 4));
console.log(multiply(3, 4));3.14159 7 12
Default Exports and Imports
Each module can have one default export:
// user.js
export default class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hi, I'm ${this.name}`;
}
}// main.js
import User from './user.js'; // No curly braces — any name works
const u = new User("Priya", 25);
console.log(u.greet());Hi, I'm Priya
Named vs Default Export Comparison
| Feature | Named Export | Default Export |
|---|---|---|
| Syntax | export const x = ... | export default ... |
| Import syntax | import { x } from ... | import AnyName from ... |
| Count per module | Multiple ✅ | One maximum |
| Name must match | ✅ Yes (or use as) | ❌ No — rename freely |
| Tree-shakeable | ✅ Better | ✅ |
| Best for | Utilities, constants | Main class/function |
Renaming with as
// Rename on export
export { add as sum, multiply as times };
// Rename on import
import { PI as pi, add as addNumbers } from './math.js';
console.log(pi);
console.log(addNumbers(5, 3));3.14159 8
Import All (import *)
import * as MathUtils from './math.js';
console.log(MathUtils.PI);
console.log(MathUtils.add(2, 3));
console.log(MathUtils.multiply(2, 3));3.14159 5 6
import * creates a namespace object — useful for grouping related utilities.
Re-exporting (Barrel Files)
A barrel file re-exports from multiple modules for clean imports:
// utils/index.js (barrel file)
export { add, multiply } from './math.js';
export { formatDate } from './date.js';
export { capitalize } from './string.js';
export { default as User } from './user.js';// Now consumers import from one place:
import { add, User, capitalize } from './utils/index.js';Dynamic Import — import()
import() is a function that loads a module lazily — at runtime, only when needed:
// Static import (always loaded):
import { add } from './math.js';
// Dynamic import (loaded on demand):
async function loadMath() {
const { add, multiply } = await import('./math.js');
console.log(add(5, 3));
console.log(multiply(5, 3));
}
loadMath();8 15
Use cases for dynamic import:
- Code splitting (load route-specific code on navigation)
- Conditional imports (only load polyfill if needed)
- User-triggered feature loading (heavy editor only when opened)
ES Modules vs CommonJS
| Feature | ES Modules (ESM) | CommonJS (CJS) |
|---|---|---|
| Syntax | import/export | require/module.exports |
| Loading | Static, async | Synchronous |
| File extension | .mjs or .js + "type": "module" | .js / .cjs |
| Tree shaking | ✅ Yes | ❌ Limited |
| Live bindings | ✅ Yes | ❌ No (copied) |
| Top-level await | ✅ Yes (ESM only) | ❌ No |
| Default in | Browser | Node.js (legacy) |
| Circular imports | ✅ Handled | ⚠️ Partial |
Module Scope and Strict Mode
All ES modules run in strict mode automatically:
// module.js (automatically strict)
x = 10; // ReferenceError: x is not definedReferenceError: x is not defined
Module scope means top-level variables are NOT global:
Live Bindings in ES Modules
ES module exports are live bindings — they reflect the current value:
// main.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1 — live binding updated!0 1
In CommonJS, require() gets a copy — it wouldn't reflect the update.
Module Execution — One Time Only
// data.js
console.log("Module loaded!");
export const value = 42;// a.js
import { value } from './data.js'; // Prints "Module loaded!"// b.js
import { value } from './data.js'; // Cached — does NOT print againModule loaded!
Modules are executed once, then cached. Multiple imports of the same module share the same instance.
Common Mistakes
❌ Mistake 1 — Missing File Extension
import { add } from './math'; // ❌ May fail in browser/native ESM
import { add } from './math.js'; // ✅ Include .js extension❌ Mistake 2 — Named Import with Wrong Name
// math.js exports: `add`
export function add(a, b) { return a + b; }// WRONG
import { sum } from './math.js'; // ❌ `sum` doesn't existSyntaxError: The requested module does not provide an export named 'sum'
// CORRECT
import { add } from './math.js';
import { add as sum } from './math.js'; // Rename after import❌ Mistake 3 — Multiple Default Exports
// WRONG — only one default export per module
export default function a() {}
export default function b() {} // SyntaxError❌ Mistake 4 — Circular Import Confusion
// a.js
import { b } from './b.js';
export const a = `a uses ${b}`;
// b.js
import { a } from './a.js';
export const b = `b uses ${a}`; // a may be undefined here!ES modules handle circular imports, but be careful — the imported value may be undefined at the time of initial execution if the circular dependency causes an ordering issue.
Interview Questions 🎯
Q1. What is the difference between named exports and default exports?
- Named export:
export const x = ...— must import with exact name (import { x }) - Default export:
export default ...— import with any name (import AnyName) - A module can have multiple named exports but only one default export
Q2. What is dynamic import and when would you use it?
import() is a function-like syntax that loads a module at runtime instead of at parse time. Returns a Promise. Use cases: code splitting (load code only when needed), lazy loading feature modules, conditional imports based on user actions.
Q3. What is the difference between ES Modules and CommonJS?
- ESM:
import/exportsyntax, static analysis (tree-shaking), live bindings, async loading, built-in to modern JS - CommonJS:
require/module.exports, synchronous, copies values (no live bindings), originally Node.js-only
Q4. What are live bindings in ES Modules?
Named ES module exports are live bindings — the import reflects the current value of the export, even if it changes after the module is imported. CommonJS require() gets a snapshot (copy) of the value at import time.
Q5. What is a barrel file in JavaScript?
A barrel file (usually index.js) re-exports multiple modules from one convenient location. Instead of import { add } from './utils/math.js' and import { format } from './utils/date.js', you can import { add, format } from './utils'.
Q6. Are ES modules automatically in strict mode?
Yes. All ES modules (import/export) run in strict mode automatically. You don't need "use strict" — it's implicit.
Q7. Can you conditionally use import inside an if statement?
Static import declarations must be at the top level — not inside if, functions, or loops. However, dynamic import() can be used anywhere:
if (needFeature) {
const { feature } = await import('./feature.js');
feature.run();
}Q8. What is tree shaking and how do modules enable it?
Tree shaking is a bundler optimization that removes unused exports from the final bundle. It works because ES module import/export is static — the bundler can analyze exactly which exports are used at build time. CommonJS require() is dynamic, making tree shaking much harder.
Key Takeaways 🔑
- ES Modules give each file its own scope — no global conflicts
- Named exports →
export const x, imported asimport { x } - Default export →
export default, imported asimport Any import * as NS→ namespace import (all exports as properties)- Dynamic
import()→ lazy loading at runtime (returns Promise) - ES modules run in strict mode automatically
- Exports are live bindings — reflect current value (unlike CommonJS copies)
- Modules execute once and are cached — multiple imports share one instance
- Barrel files (
index.js) centralize re-exports for cleaner import paths
Summary
JavaScript modules are the official solution to the global scope pollution problem. ES Modules (import/export) give each file its own isolated scope, with explicit exports and imports creating a clear dependency graph. Key concepts: named exports (multiple per module, imported with exact name), default exports (one per module, imported with any name), dynamic import() for lazy loading, and live bindings (exports reflect current values). ES Modules differ from CommonJS primarily in loading strategy (static vs dynamic), binding type (live vs copied), and tree-shaking support. Understanding modules is essential for modern JavaScript development with bundlers like webpack, Vite, and Rollup.
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for JavaScript Modules - Complete Guide with import export Examples.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this JavaScript Master Course topic.
Search Terms
javascript-complete-guide, javascript master course, javascript, complete, guide, advanced, modules, javascript modules - complete guide with import export examples
Related JavaScript Master Course Topics