skip to content

When designing an ES module's public surface, what are the practical tradeoffs between exposing values as named exports versus a single default export?

level: seniorimportance: should knowfreq 45%

answer

  1. who gets to choose the name?
  2. grep and rename tooling follow names
  3. one file, one obvious artifact
  4. the same value under three aliases
  5. anonymous default is named "default"

basics

~20 s

Named exports keep the public name under the author's control, so identifiers stay consistent, greppable and safely renameable by tooling. A default export makes a one-artifact file read cleanly but hands the naming decision to every importer, which invites drift.

solid answer

~60 s

With a named export the author owns the name: importers must spell it exactly, so the same value is called the same thing everywhere, `grep` finds all uses, and an editor's rename refactor can propagate across a project. A default export lets each importer choose its own identifier, which is convenient for a file that exports exactly one obvious thing but means the same class can appear as `Button`, `Btn` and `UIButton` in three files, and a rename at the source propagates to nothing. Defaults also blur stack traces — `export default () => {}` gives the function the name `default`. Against that, defaults keep single-artifact files terse and free consumers from a name they may not like. A common house rule is: named exports for shared libraries and anything a barrel forwards, a default only for files whose entire purpose is one artifact — and enforce it with a lint rule rather than review habit, since mixing both forces consumers to use two import shapes for one file.

go deeper

for a junior

Know that a default import name is chosen by whoever imports it, while a named import must match, and that this is the root of the whole debate.

for a middle

Explain the concrete consequences: search and rename tooling follow named exports, misspelled named imports fail at link time, and an anonymous default function ends up named "default" in stack traces.

for a senior

Take a position and defend it with codebase-scale evidence — naming drift across importers, barrel forwarding special cases, and the friction of adding a named export beside an existing default. Say how you would enforce the rule mechanically.

for a principal

Own the convention as public API policy: what the package's entry point exposes, how renames are communicated to consumers, and why a lint rule rather than review habit is what keeps the surface coherent as the organisation grows.

## The decision, stated precisely Both forms put a value in a module's export table; the only real difference is **who owns the name**. A named export fixes it at the source. A default export leaves it blank and lets every consumer fill it in. Every tradeoff below is a consequence of that single asymmetry. ## What named exports buy **Consistency.** `import { Button } from './button.js'` must spell `Button`. Ten importers therefore agree on the identifier, which makes code reviews, conversations and search all line up on one word. **Searchability.** Grepping for a named export finds its declaration and every import of it. With a default, the source declares one name and the importers may use any other, so text search only ever finds part of the picture. **Refactorability.** Editor rename operations follow the export entry. Rename a named export and every importing site updates. Rename the local variable behind a default export and nothing downstream changes, because nothing downstream referred to that name. **Failure at link time on typos.** A misspelled named import cannot be resolved and is reported before the module runs. A default import always resolves as long as the module has *any* default, so a consumer who grabbed the wrong file gets a value, just not the one they meant. ## What default exports buy **A single obvious artifact.** For a file whose entire reason to exist is one class, one component or one configuration object, `export default` says so. There is no second export to wonder about, and no repetition of the name in the declaration, the export clause and the import. **Consumer-side freedom.** When two dependencies both export something called `Client`, a default import lets each consumer disambiguate at the import site without an `as` clause. With named exports the same thing is achievable — `import { Client as StripeClient }` — but it is a keystroke or two more. **Terse authorship.** `export default function () {}` needs no name at all. That convenience is also its trap: the spec gives such an anonymous function the name `default`, so it shows up as `default` in stack traces and `Function.prototype.name`, which is worse for debugging than a real name. Writing `export default function log() {}` restores the name. ```js export default () => {}; // the exported arrow's .name is "default", not "" ``` ## The naming-drift cost in practice The cost of a default export is rarely visible on day one; it accumulates. A widely used component acquires three or four aliases across a large codebase. Someone renames the file, the local declaration and the concept, and the old name survives in every consumer because nothing ever forced it to change. New joiners search for the current name and miss half the call sites. None of this is a bug, and none of it is caught by a test — which is exactly why it is a convention question rather than a correctness question. ## Mixing both A module may have a default *and* named exports, and doing so is legal and common. The price is that consumers must use two import shapes for one file — `import Button, { SIZES } from './button.js'` — and that barrel re-exports become inconsistent, because `export * from` forwards the named exports but skips the default. If a package's barrel is the intended entry point, a named-only surface removes an entire class of forwarding special cases. ## A workable policy Most teams that have had this argument land somewhere near: named exports for shared libraries, utilities and anything a barrel forwards; a default reserved for files that are genuinely one artifact and are imported directly. Whatever you pick, encode it as a lint rule so it holds when the team grows — a convention enforced only in review erodes at exactly the rate people stop reviewing carefully. Also state the rule for the awkward middle: a module that today has one export but will plausibly grow a second is better off named from the start, because adding a named export beside an existing default is an ergonomic downgrade for every consumer. ## Answering this in an interview Lead with the ownership asymmetry — the author names it versus the consumer names it — then give two concrete consequences on each side, then say what rule you would actually enforce and how. An answer that is only "defaults are bad" is weaker than one that says *why* the naming decision matters, and names the case where a default still earns its place.

  • Why does adding a named export to a module that already has a default export create friction for consumers?
    Because one file now needs two import shapes: `import Button, { SIZES } from './button.js'`. It also complicates forwarding, since `export * from` carries the named exports but skips the default, so any barrel has to add an explicit `export { default as … } from` line. A module that will plausibly grow a second export is better off named-only from the start.
  • What is the name of the function produced by `export default () => {};`, and why does it matter?
    Its `name` is the string `"default"` — the spec names an anonymous function definition in that position after the export name. It matters for debugging: stack traces and profiler output show `default` rather than something meaningful, and several such modules all look alike. Giving the function a real name, or assigning it to a named const first, restores a useful label.
  • Is there a case where you would still choose a default export in a shared library?
    Yes — for a package whose whole surface is one artifact, such as a single middleware factory or a single component, where the consumer will always import exactly one thing and naming it themselves is genuinely convenient. The judgment turns on whether the module can plausibly grow a second export; if it can, name it now and avoid the mixed-shape entry point later.

saying these in an interview costs you the question

  • Says default exports are simply wrong with no reason given
  • Claims a default export cannot be renamed by the importer
  • Thinks renaming the source variable updates default importers
  • Assumes an anonymous default function has an empty name
  • Believes mixing default and named exports costs consumers nothing

context