AlgoInsight

Free lesson · JavaScript Basic to Advance

Default Exports vs Named Exports

Easy5 min read

The Two Kinds of Exports

ES Modules have two distinct export styles: named exports and default exports. They look similar but behave differently, and the choice between them has real consequences for how your modules are used.

// named.js — named exports
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
// default.js — default export
export default function Logger() {
  // ...
}
// Importing each style
import { PI, add, multiply } from "./named.js";
import Logger from "./default.js";   // no curly braces for default

The curly braces are the visible difference. Named exports use { }. Default exports don't. Underneath, the mechanisms are different too — named exports can have many per module, default exports have exactly one.

This Topic covers the differences, when to use each, and the strong opinions the JavaScript community has about which is "better" (spoiler: most modern style guides prefer named exports).

How Default Exports Work

A module can have at most one default export. It's declared with the export default keyword:

// logger.js
export default class Logger {
  log(msg) { console.log(msg); }
}

When importing a default export, you choose the name on the import side:

// Both are valid — the importer names it
import Logger from "./logger.js";
import MyLogger from "./logger.js";
import WhateverIWant from "./logger.js";

The import side gets to choose because default exports don't have an inherent name. They have a value — a function, a class, an object — but no name attached to that value. The importer picks the name when they use the import.

This is the source of one of default exports' major weaknesses: the import name is arbitrary. Three files in a codebase can import the same default export under three different names. Refactoring becomes harder because there's no consistent name to search for.

Interactive animation: The same function, three vocabularies - each default import picks its own name, while a named import keeps one.

How Named Exports Work

Named exports always have a name — that's why they're called "named." When you import them, you have to use that name (or explicitly rename them):

// utils.js
export function formatDate(date) { /* ... */ }
export function parseDate(str) { /* ... */ }
export const DATE_FORMAT = "YYYY-MM-DD";
// Must match the name (or use 'as' to rename)
import { formatDate, parseDate, DATE_FORMAT } from "./utils.js";

// Renaming requires explicit syntax
import { formatDate as fmt } from "./utils.js";

The named import contract is stricter. The importer must know the exact names exported by the module. This stricter contract is what makes named exports easier to refactor and analyze — there's a fixed, knowable name for every value.

Interactive animation: What each form gives your tools - autocomplete, rename refactors and typo detection, named exports against default.

You Can Mix Both Styles

A module can have both named exports and a default export:

// react-style.js
export const VERSION = "18.0";

export function useState(initial) { /* ... */ }
export function useEffect(callback, deps) { /* ... */ }

export default function React() { /* the main thing */ }
// Import both
import React, { useState, useEffect, VERSION } from "./react-style.js";

// Default goes first (no braces), named imports follow in braces

This is the pattern React itself uses. The main React object is the default export. The hooks and utilities are named exports. Importing both styles in one statement is allowed and common.

graph TD
    A[Module file] --> B[Named exports<br/>any number, must match names]
    A --> C[Default export<br/>at most one, name chosen on import]

    B --> D[import name from braces]
    C --> E[import AnyName from quotes]

    style A fill:#7c3aed,color:#fff
    style B fill:#10b981,color:#fff
    style C fill:#f59e0b,color:#fff
This diagram contrasts the two export styles. A module can have any number of named exports, and importers must use the exact names. A module can have at most one default export, and the import side chooses whatever name they want. A single module can have both — named exports plus one default — and a single import statement can pull in both.

When to Use Default Exports

The traditional case for default exports: when a module exports exactly one main thing. A class for a specific concept. A configuration object. A React component that's the file's whole purpose.

// UserCard.jsx — a React component
import React from "react";

export default function UserCard({ user }) {
  return <div>{user.name}</div>;
}

This is the React community's strong convention. Each component file has the component as its default export. The file is "about" the component. Other consumers of the file are importing "the thing this file is for."

The reasoning: in some patterns, having a single clear "main thing" per file makes the codebase easier to navigate. Files become "the X file" — the User file, the Card file, the Button file — and the default export is what you get when you import from that file.

The Case Against Default Exports

A strong counter-argument: default exports are worse for almost everything except first-glance convenience.

1. They make refactoring harder. The same value can be imported under different names across the codebase. Searching for usage requires knowing all the variations. Renaming the value requires updating every import name.

2. They confuse tools. Static analysis, type checking, and IDE auto-completion all work better with named exports because the names are stable contracts.

3. They make typo bugs silent. If you typo a named import, you get an immediate error. If you typo a default import's name, you just have a misnamed local variable — the import still succeeds.

4. They encourage "mystery imports". import Thing from "./module" tells you almost nothing about what Thing is. import { specificFunctionName } from "./module" tells you exactly what you got.

Many modern style guides — including ESLint's prefer-named-exports rule, Airbnb's style guide, and Google's TypeScript guide — recommend avoiding default exports for these reasons.

In my Microsoft work on VS Code, the team prefers named exports for exactly these reasons. Renaming a function in a 200-file codebase is a one-command refactor when everything uses named exports. With default exports, it's a manual search through every importer.

A Pragmatic Position

My recommendation, which I've seen consistently in modern teams:

For my own projects, I default to named exports unless there's a clear reason to use default. The discipline produces more maintainable code over time.

Re-exports With Defaults

When you re-export a default from another module, the syntax has some specific forms (we cover re-exports more in Topic 11.4):

// index.js — barrel file
export { default as UserCard } from "./UserCard.js";   // re-export as named
export { default } from "./MainComponent.js";           // re-export as default

The first form takes a default export from one file and re-exports it as a named export of the current file. The second form passes a default export through unchanged. Both are used in real library code.

Common Pitfalls

Warning: The default vs named export traps.

Inconsistent naming of default imports. The same default export imported under three names across a codebase makes refactoring hard. Stick to one name as a team convention.

Forgetting that default exports lose their "name". export default function foo() {} exports the function, but the name foo doesn't travel with it. Importers see whatever they choose.

Mixing them confusingly in one file. Multiple named exports plus one default in the same file works but can be hard to read. Be deliberate about whether your file is a "one main thing" or a "collection of related things."

Auto-import tools defaulting to wrong names. Many IDE auto-imports for default exports pick a name based on the filename. This produces inconsistent names if filenames don't match the value name. Review auto-imports before committing.

Why This Matters in Interviews

Interview: "What's the difference between a default export and a named export?" is a standard interview question at mid-levels. The expected answer covers the syntax difference, the at-most-one rule for defaults, the importer-chooses-name behavior, and ideally a mention that named exports are preferred in modern style for refactorability reasons.

Topic 11.7 — Interview Lens: Module System Questions — covers the deeper treatment.

Try It

Exercise: Take a file with this structure:

// utility.js
export default function processData(data) { /* main logic */ }

export function validate(data) { /* helper */ }
export function sanitize(data) { /* helper */ }
export const VERSION = "1.0";

Refactor it to use only named exports — no default. Then import it into another file and verify the imports look reasonable. Ask yourself: which version is easier to understand from the import side? Which version makes it easier to find usages across a codebase?

Once you've done this exercise, the trade-off between default and named exports is concrete rather than abstract.

Back to JavaScript Basic to Advance