JavaScript Module Live Bindings
ES module imports are live bindings: an imported name remains connected to the binding maintained by the exporting module. Reading the import later can therefore observe changes made by the exporter, rather than a one-time copied value.
In JavaScript ES modules, an `import` creates a binding to a name exported by another module. The importing module does not receive an independent local variable containing the export's current value; the imported name remains associated with the export binding.
If the exporting module changes its own binding, subsequent reads through the import observe that updated value. This is especially relevant for exported variables declared with `let`, `const`, or `var`: the importer can read the binding, but it cannot assign to the imported name directly.
For example, an exporter can increment an exported `let` variable, while another module periodically reads that imported name. The reader sees the current exported value. This behavior helps ES modules preserve a shared connection between module interfaces instead of requiring explicit synchronization or copying.
Live bindings are distinct from object-reference behavior. If a module exports an object and code mutates a property of that object, consumers observe the mutation because they are using the same object reference. Live bindings also cover replacement of an exported binding itself, such as assigning a new number to an exported `let` variable.
The rule applies to ES module imports and exports, not to every JavaScript module system. CommonJS `require()` has different semantics: destructuring or assigning a property from the returned exports object can produce a local value that is no longer a live binding to the original property.
Key points
- ES module imports are live bindings to exported names.
- The importer cannot reassign an imported binding.
- Changes to an exported `let` or `var` binding can be observed by importers.
- Live bindings are different from merely sharing a mutable object reference.
- CommonJS and ES module binding semantics should not be assumed to be identical.
Interview questions
What does it mean that ES module imports are live bindings?
A live binding means the imported name remains connected to the binding in the exporting module. It is not simply a snapshot taken when the import statement is evaluated. If the exporter changes an exported `let` binding, a later read through the import sees the new value. The importing module can read the binding but cannot assign to that imported name.
Can an imported variable be reassigned by the importing module?
No. An imported binding is read-only in the importing module. For example, `count = 5` is invalid when `count` was imported from another module. The exporting module can change an exportable `let` or `var` binding, and importers can observe that change.
Why are live bindings useful in modular JavaScript applications?
They allow modules to expose state without requiring consumers to manually synchronize copied values. A consumer can import a binding and observe changes made by the exporting module. This also supports cyclic module relationships more naturally than a model based only on copied values. The important distinction is that the module interface remains connected to the source binding.
How are live bindings different from exporting an object and mutating it?
With an exported object, consumers can observe mutations because they hold a reference to the same object. A live binding is a module-level connection to an exported name. Consequently, a live binding can observe reassignment of an exported `let` variable, whereas an ordinary copied variable would retain its previous value. The two mechanisms can therefore produce similar observations in some cases while relying on different semantics.
What is a common mistake when comparing ES modules with CommonJS?
A common mistake is treating `import` and `require()` as equivalent binding operations. ES module imports establish bindings to exported names, while CommonJS exposes values through an exports object and consumer code may copy properties into local variables. For example, destructuring a CommonJS export creates local variables that do not automatically become ES-style live bindings. Correct reasoning therefore requires identifying which module system and operation are actually being used.