ES Modules

10 questions found

What's the difference between a named export and a default export?

Beginner
A module can have any number of named exports (imported with matching names in curly braces), but at most one default export (imported with any name you choose, no braces required).
// math.js
export const PI = 3.14;
export default function add(a, b) { return a + b; }

// main.js
import add, { PI } from './math.js';
Real-world example Using a default export for a component's main class/function, and named exports for its helper utilities.

Common follow-ups: Can a module have both a default export and named exports at the same time?

Design Patterns in JavaScript

How do you import everything from a module under one namespace object?

Beginner
Use import * as name from '...' to collect all of a module's named exports into a single object, accessed as name.exportedThing.
import * as MathUtils from './math.js';
console.log(MathUtils.PI);
MathUtils.default(2, 3); // calling the default export
Real-world example Importing an entire utility library under one namespace to avoid dozens of individual named imports.

Common follow-ups: Does this also include the default export, and if so, under what key?

Functional Programming

What's the difference between ES Modules and CommonJS (require/module.exports)?

Intermediate
ES Modules use static import/export syntax analyzed at parse time (enabling tree-shaking), run in strict mode by default, and support top-level await. CommonJS's require() is dynamic and synchronous, resolved at runtime, and is Node.js's traditional module system.
// ES Module
import { readFile } from 'fs/promises';

// CommonJS
const { readFileSync } = require('fs');
Real-world example Migrating an older Node.js codebase from require() to import to enable modern bundler optimizations like tree-shaking.

Common follow-ups: How do you use CommonJS modules from within an ES Module file, or vice versa?

Package Management Bundlers & Transpilation (npm Webpack/Vite Babel)

What is tree-shaking and why does it require ES Module syntax?

Intermediate
Tree-shaking is a bundler optimization that removes unused exports from the final bundle. It relies on ES Modules' static, analyzable import/export structure — bundlers can determine at build time exactly what's used, which isn't reliably possible with CommonJS's dynamic require() calls.
// Only 'add' gets bundled if 'subtract' is never imported anywhere
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
Real-world example Shipping a smaller JS bundle by importing only the specific lodash functions used, instead of the whole library.

Common follow-ups: Why can side effects in a module prevent tree-shaking from working correctly?

Package Management Bundlers & Transpilation (npm Webpack/Vite Babel)

How do dynamic imports (import()) differ from static import statements?

Intermediate
import() is a function-like expression that returns a Promise resolving to the module, and can be called anywhere at runtime — including conditionally — unlike static import which must appear at the top level and is resolved before any code runs.
button.addEventListener('click', async () => {
  const { openModal } = await import('./modal.js');
  openModal();
});
Real-world example Lazy-loading a heavy chart library only when the user navigates to a page that actually needs it.

Common follow-ups: How does this enable code-splitting in bundlers like Webpack or Vite?

Package Management Bundlers & Transpilation (npm Webpack/Vite Babel)

Why are ES Module imports 'hoisted' and evaluated before the rest of the module's code runs?

Intermediate
The JS engine statically analyzes all import declarations first and resolves/executes the imported modules before running the importing module's own top-level code, ensuring all bindings are available regardless of where the import statement appears in the file.
console.log(PI); // works even though import is written below
import { PI } from './constants.js';
Real-world example Relying on imported constants or functions being available immediately at the top of a file's execution.

Common follow-ups: Does this mean import order in the file affects behavior?

Scope Hoisting & Closures

What does 'live binding' mean for ES Module exports, and how does it differ from CommonJS?

Advanced
ES Module imports are live, read-only views onto the exporting module's variable — if the exporting module later reassigns the exported variable, every importer sees the updated value automatically. CommonJS exports are copied values at the time of require(), so later changes aren't reflected.
// counter.js
export let count = 0;
export function increment() { count++; }

// main.js
import { count, increment } from './counter.js';
increment();
console.log(count); // 1 — reflects the live update
Real-world example Sharing a mutable, always-current configuration value across multiple modules without a getter function.

Common follow-ups: Can an importing module reassign a live-bound imported variable itself?

Scope Hoisting & Closures

How does top-level await work, and what constraint does it place on modules that import that module?

Advanced
Top-level await lets a module pause its own evaluation until a Promise resolves, without wrapping code in an async function. Any module that imports a module using top-level await will itself wait for that import to finish resolving before continuing.
// config.js
const response = await fetch('/config.json');
export const config = await response.json();

// main.js — implicitly waits for config.js to finish
import { config } from './config.js';
Real-world example Loading remote configuration once at module initialization time before the rest of the app starts using it.

Common follow-ups: Why was top-level await NOT allowed in CommonJS modules?

Promises & async/await

What are import maps and what problem do they solve for browser-native ES Modules?

Advanced
Import maps let you use bare specifiers (like import 'lodash') directly in the browser without a bundler, by mapping those short names to actual URLs in a <script type='importmap'> block — otherwise browsers require full relative or absolute URLs.
<script type="importmap">
{
  "imports": { "lodash": "https://cdn.example.com/lodash.js" }
}
</script>
<script type="module">
  import _ from 'lodash';
</script>
Real-world example Running a bundler-free, browser-native ES Module app while still using clean package-style import names.

Common follow-ups: Do all browsers support import maps natively?

Package Management Bundlers & Transpilation (npm Webpack/Vite Babel)

Why can circular module imports (Module A imports B, which imports A) cause subtle bugs?

Advanced
Because of live bindings and evaluation order, one of the two modules will see an export from the other as undefined (or an incomplete value) at the moment its own top-level code runs, since the other module hasn't finished executing yet — the value often becomes correct only later, after both modules finish loading.
// a.js
import { b } from './b.js';
console.log(b); // may log undefined here
export const a = 'A';

// b.js
import { a } from './a.js';
export const b = 'B';
Real-world example Debugging a mysterious 'undefined is not a function' error caused by two modules that import each other.

Common follow-ups: How do you refactor code to break a circular dependency?

Design Patterns in JavaScript