In ES modules, what is the difference between a named export and a default export, and how does the import syntax differ for each?
answer
- one export table, two spellings
- default is just a name
- importer picks the default's name
- braces must match the exported name
- at most one default per module
basics
~20 sA named export binds a value to a specific export name that importers must match exactly, using braces: import { parse } from './m.js'. A default export registers the value under the name "default", and the importer may bind it to any identifier it likes.
solid answer
~50 sBoth are entries in the same export table; the only difference is the name. `export const parse = ...` or `export { parse }` registers the export name `parse`, and an importer must spell it identically inside braces — `import { parse } from './m.js'` — optionally renaming with `import { parse as p }`. `export default value` registers the export under the reserved name `default`, and the importer writes it without braces and chooses the local name freely: `import anythingAtAll from './m.js'`. Because it is just a name, `import { default as parse }` is exactly equivalent to the default import, and a namespace import exposes it as `ns.default`. A module may have many named exports but at most one default, and `export default` takes a function or class declaration or an expression — so `export default const x = 1` is a syntax error.
go deeper
Be ready to write both import shapes from memory and say which needs braces. Know that a default import name is yours to choose, while a named import must match the exported spelling exactly.
Explain that export default just registers the export name "default", and show the equivalences: import { default as x }, export { x as default }, and ns.default after a namespace import. Know which combined import forms are grammatically legal.
Show that unresolved import names are reported at link time rather than surfacing as undefined at runtime, and explain what that buys a codebase in early failure. Be able to justify a house rule on default exports for shared modules.
Own the convention for the organisation: whether shared packages expose named exports only, how that interacts with barrel files and public API stability, and how you enforce it with lint rules rather than review habit.
## Two spellings, one mechanism Every ES module carries a table of export entries that map an **export name** (a string) to a binding inside the module. Named exports use whatever identifier you pick. `export default` is not a second mechanism bolted on beside it — it simply registers an entry under the literal name `"default"`. Almost every behaviour below falls out of that one fact. ## The named-export forms There are two spellings, and they are interchangeable: ```js // geometry.js export const PI = 3.14159; // declaration and export together export function area(r) { return PI * r * r; } export class Circle {} const helper = (x) => x * 2; export { helper }; // export clause, written separately export { area as circleArea }; // the same value under a second name ``` The export clause form is useful when you want the export list in one visible place, or when you want to expose a binding under a different public name than its internal one. A single binding may be exported under several names; each `as` adds another entry pointing at the same binding. ## The default form ```js // logger.js export default function log(msg) { console.log(msg); } export const LEVEL = 'info'; // defaults and named exports coexist ``` `export default` accepts a function declaration, a class declaration, or **an expression** — so `export default { retries: 3 }` and `export default 42` are both legal, while `export default const x = 1;` is a SyntaxError because a `const` declaration is not an expression. A module may declare at most one default; a second one is a SyntaxError. The clause equivalent is `export { helper as default }`. ## The import forms ```js import log from './logger.js'; // default, name chosen by the importer import whateverIWant from './logger.js'; // identical to the line above import { LEVEL } from './logger.js'; // named, must match exactly import { LEVEL as level } from './logger.js'; // named, renamed locally import log, { LEVEL } from './logger.js'; // default plus named import log, * as logger from './logger.js'; // default plus namespace import * as logger from './logger.js'; // logger.LEVEL, logger.default import './setup.js'; // no bindings, side effects only ``` One combination is *not* allowed: a namespace import cannot sit beside a named-import list, so `import { LEVEL }, * as logger from './logger.js'` is a SyntaxError. Note also that `import { default } from './logger.js'` fails, because `default` is a reserved word and cannot be a binding name — you must write `import { default as log }`. ## Mismatched names fail structurally, not silently Because imports are resolved before any module code runs, a named import that does not correspond to an export is reported as a SyntaxError at link time, before the importing module executes a single statement. It does not quietly produce `undefined`, and it does not wait until you call the value. The same applies to a default import from a module that declares no default: `default` is just a name, and an unresolvable name is an error. Candidates who expect `undefined` are carrying over intuition from destructuring a plain object. ## Which to reach for Named exports keep the public name of a value under the author's control: the importer must spell it correctly, editors can auto-import and rename it across a project, and the name is greppable. A default export lets a file present a single obvious thing — a component, a class, a configuration object — without repeating its name, but the naming decision moves to each importer, so the same value ends up called different things in different files. Many teams standardise on named exports for shared libraries for exactly that reason, while single-artifact files keep a default. Mixing both in one module is legal and common, but it forces consumers to use both import shapes for one file. ## A quick self-check Given `export default class Button {}` in `button.js`, `import Btn from './button.js'` and `import { default as Btn } from './button.js'` bind the same class, and `import * as m from './button.js'` gives an object whose `default` property is that class. If you can state that, you have the model right.
- Can a module have both a default export and named exports at the same time?Yes. `export default class Button {}` alongside `export const SIZES = [...]` is completely legal, and a consumer can pull both in one statement: `import Button, { SIZES } from './button.js'`. The only cap is that there can be at most one default entry per module; named exports are unlimited.
- Why is `export default const config = {}` a syntax error when `export const config = {}` is fine?Because `export default` is followed by a function declaration, a class declaration, or an expression — never a variable declaration. `const config = {}` is a declaration, so it does not fit the grammar. Write `const config = {}; export default config;`, or just `export default {}` if the local name is not needed.
- What does `import './polyfills.js'` with no bindings actually do?It is a side-effect-only import: the module is resolved, linked and evaluated exactly once, but nothing is bound in the importing scope. It is the right form for a module whose whole purpose is to run — registering a polyfill, installing a global handler, importing a stylesheet in a bundler pipeline.
saying these in an interview costs you the question
- Thinks a missing named import quietly yields undefined
- Says default exports are a different mechanism from named exports
- Believes a module can have several default exports
- Claims named imports can be renamed but default imports cannot
- Writes import { default } from './m.js' and expects it to parse