ES modules provide static import/export syntax that the engine analyzes at parse time. Imports are live, read-only bindings to the exporter's variables. Modules evaluate once, are cached, and handle circular dependencies through partially-initialized module records.
ES modules use import and export statements that the engine resolves statically before any code runs. Unlike CommonJS require(), which is a runtime function that copies values, ES imports create live bindings that reference the exporter's actual variable slot.
The module loading process has distinct phases: parsing (build the module graph), linking (connect import bindings to export slots), and evaluation (run the module code top-to-bottom). Each module evaluates exactly once and is cached. Subsequent imports of the same module return the cached instance.
Live bindings mean the import always reads the current value of the export. If the source module mutates an exported let variable, all importers see the change immediately. However, imports are read-only from the importer's perspective. Attempting to reassign an import throws a TypeError.
Interview framing: define Modules & Imports in one sentence, then explain one concrete runtime behavior and one common pitfall with a short code example.