Detailed explanation
In JavaScript, a classic script and an ES module are different ways to execute code and manage dependencies. A script traditionally runs in a shared global environment, while a module is a separate unit with its own top-level scope. Modules explicitly expose values with export and consume values with import. This makes dependencies visible and helps prevent naming collisions as applications grow. In browsers, an external classic script uses <script src="app.js"></script>, whereas an ES module uses <script type="module" src="app.js"></script>.
Scope and execution rules are important differences. In a browser classic script, top-level var declarations and function declarations generally become properties of window; top-level let and const create global lexical bindings instead. Modules keep all these declarations local unless explicitly exported. Modules also run in strict mode automatically, so operations such as assigning to an undeclared variable throw errors. At the top level, this is undefined in a module but refers to the global object in a browser classic script. Exporting a value does not attach it to window.
Static import and export declarations describe a dependency graph before execution. The runtime resolves and links dependencies, then evaluates them in dependency order, with special handling for cycles and asynchronous evaluation. Imports are live bindings: they reflect changes made by the exporting module, but importers cannot reassign those bindings. For the supplied example, utils.js provides both a named export called add and a default export. The statement import add, { add as named } from './utils.js' imports the same function under two local names. A default import can use any local name; a named import must identify the exported name, optionally using an alias.
Browser module scripts are deferred by default: fetching does not block HTML parsing, and normal execution waits until parsing finishes. Classic scripts without async or defer can block parsing. Static module structure also enables build tools to analyse dependencies and perform tree shaking, although unused-code removal depends on tooling and side effects. Interviewers expect these distinctions, plus awareness that import() dynamically loads modules and can also be used from classic scripts.
Key points to remember
- ES modules have module-local scope; browser classic scripts share a global environment.
- Modules are always strict, and their top-level this is undefined.
- Static import/export declarations enable dependency linking and support bundler tree shaking.
- Browser module scripts are deferred by default; ordinary classic scripts can block HTML parsing.
- Named and default exports use different import syntax; imported bindings are live and cannot be reassigned by importers.
Common mistakes candidates make
- Claiming every top-level classic-script declaration becomes a window property; let and const do not.
- Saying modules automatically remove unused code; tree shaking is normally a build-tool optimisation affected by side effects.
- Assuming browser script rules apply unchanged to Node.js, where CommonJS files also have their own scope.
Likely follow-up questions
How does static import differ from dynamic import()?
Static import declarations appear only at module top level and make dependencies discoverable before execution. Dynamic import() is an expression that returns a promise for a module namespace object, enabling conditional or on-demand loading.
What does it mean that imports are live bindings?
If a module exports let count and later updates count, importers observe the updated value. Importers cannot assign to count themselves, although they can mutate properties of an imported object if that object permits mutation.
Does importing the same module twice execute it twice?
Within the same module-loading environment, imports resolving to the same module identity normally share one evaluated module instance. Different resolved URLs, such as URLs with different query strings, can represent separate module instances.
Interview tip: Explain scope, strict mode, loading behaviour and import/export first, then use the supplied example to distinguish named and default imports.
