In an ES module, what does `export * from './utils.js'` actually re-export, and what does it leave out?
answer
- star forwards names, not everything
- one export is deliberately left behind
- nothing lands in the local scope
- two stars, same name, ambiguous
- export * as ns is a different statement
basics
~10 sIt forwards every named export of ./utils.js under the same names, and deliberately excludes that module's default export. It also creates no local bindings, so the re-exporting file cannot use those names itself.
solid answer
~50 s`export * from './utils.js'` adds every **named** export of the target to the current module's export list, under the same names. The default export is explicitly not included — if you want it forwarded you must say so with `export { default } from './utils.js'` or `export { default as Utils } from './utils.js'`. The star also creates nothing in the local scope: the re-exporting file cannot reference those names in its own code. Two important collision rules: an explicit export declared locally takes precedence over a star-provided name of the same spelling, and if two `export *` declarations supply the same name from different bindings, that name becomes ambiguous — it is dropped from the star set, and any module that tries to import it fails at link time. `export * as utils from './utils.js'` is a different statement: it exports one name whose value is the target's namespace object, and that object does include `default`.
code
javascript · 11 lines// button.js
export const SIZES = ['sm', 'lg'];
export default class Button {}
// index.js (barrel)
export * from './button.js'; // forwards SIZES only
export { default as Button } from './button.js'; // default must be explicit
// consumer.js
import { SIZES, Button } from './index.js';
console.log(SIZES, typeof Button); // ['sm','lg'] 'function'go deeper
Know that this line forwards another module's named exports so consumers can import them from one place, and that the default is not carried along automatically.
Explain the four mechanics: same names forwarded, default excluded, no local binding created, and duplicate names across two stars becoming ambiguous rather than silently resolved.
Weigh star re-exports against explicit lists for a package's public surface — implicit API growth, where collision errors surface, and the fact that starring a folder still evaluates every file in it.
Own the barrel policy for a shared package: what is exported at all, how the surface is kept intentional as it grows, and how a rename inside the package is prevented from becoming an unannounced break for consumers.
## What the star form is for `export * from './utils.js'` is a re-export declaration. It says: everything this other module exposes by name, my module now exposes too, under the same names. It exists mainly for barrel files — an `index.js` that gathers a folder's public surface into one specifier so consumers write `import { parse, format } from './text/index.js'` instead of reaching into individual files. ```js // text/index.js export * from './parse.js'; export * from './format.js'; ``` ## The default export is not included This is the fact interviewers are usually fishing for. The star forwards named exports only; the target's `default` is skipped. It has to be: if it were included, two starred modules that both have a default would collide on the name `default` in every barrel, and the barrel's own default would be impossible to control. Forwarding a default is therefore an explicit act: ```js export { default } from './button.js'; // this module's default is button's export { default as Button } from './button.js'; // exposed as a named export instead ``` ## No local bindings are created A re-export is not an import. `export * from './parse.js'` does not put `parse` into the barrel's scope, so code inside the barrel cannot call it — referencing it throws a ReferenceError. If the file needs to *use* a value as well as forward it, it must import it separately. ## Collisions and ambiguity Two rules govern name clashes, and both are worth being able to state. First, a local or explicit export always wins over a star. Given: ```js export * from './a.js'; // a.js exports parse export const parse = myOwnParse; ``` consumers get the locally declared `parse`; the starred one is shadowed, not an error. Second, if two star declarations supply the same export name from **different** bindings, the name is ambiguous. It is excluded from the star set, and it is not merely undefined: a module that explicitly imports that name from the barrel fails during linking with a SyntaxError, before anything runs. Importantly, if both stars ultimately resolve to the *same* binding — for example both modules re-export it from a common source — there is no ambiguity at all. The practical consequence is that a barrel built from many `export *` lines is quiet about collisions until someone imports the colliding name, at which point the error points at the consumer rather than at the barrel that caused it. Explicit `export { … } from` lists cost a few more keystrokes and make the surface reviewable. ## The namespace re-export is a different statement ```js export * as utils from './utils.js'; ``` This was added in ES2020, and it does something else entirely: it exports **one** name, `utils`, whose value is the target module's namespace object. Consumers write `import { utils } from './index.js'` and reach members as `utils.parse`. Because a namespace object exposes every export as a property, `utils.default` *is* available here — the exclusion described above applies to the bare star form, not to a namespace object. ## Ordering and evaluation A re-export is not conditional and cannot be nested inside a block or a function; like every import and export declaration it must sit at the module's top level. And starring a module still causes that module to be evaluated when the graph runs, so a barrel that stars a dozen files pulls all of them in, even if the consumer only wanted one name from one of them. That is a cost to weigh against the ergonomics, especially for a large public barrel. ## What to say in an interview A complete answer names four things: all named exports are forwarded under the same names; the default is excluded and must be forwarded explicitly; no local binding is created; and duplicate names across two stars become ambiguous rather than last-one-wins. Adding that `export * as ns from` is a distinct form that exports a single namespace-object name shows you know the ES2020 addition rather than confusing it with the bare star.
- Two `export *` lines in a barrel both provide a name called `format`. What happens?If they resolve to different bindings the name is ambiguous: it is excluded from the barrel's star exports, and any module that explicitly imports `format` from the barrel fails at link time with a SyntaxError. If both stars trace back to the same original binding there is no conflict. Declaring `export { format } from './one.js'` explicitly in the barrel resolves it, since explicit exports take precedence.
- Why would a team prefer explicit `export { a, b } from './x.js'` lists over `export * from './x.js'` in a barrel?Because the star makes the public surface implicit: adding an export in a leaf file silently widens the package's API, and name collisions only surface when a consumer trips over them. An explicit list is reviewable in a diff, keeps the surface intentional, and puts any conflict error in the barrel where it was caused.
saying these in an interview costs you the question
- Says export * also forwards the default export
- Thinks a duplicate name is resolved last-one-wins
- Assumes the re-exporting file can call the starred names
- Confuses export * as ns with the bare export * form
- Believes a barrel avoids evaluating the starred modules