In a CommonJS file, `const pkg = require('some-package')` returns an object shaped like `{ __esModule: true, default: [Function] }`, and calling `pkg()` throws `TypeError: pkg is not a function`. What produced that shape, and what does it tell you about how the package was built?
answer
- look at the shape, not the name
- who writes a default property
- a non-enumerable marker is present
- lowered from ESM to CommonJS
- the container versus the callable
basics
~20 sThe package was written as ES modules and compiled down to CommonJS. Compilers put an ESM default export on the exports object under a default property and add an __esModule marker, so the callable value is pkg.default, not pkg.
solid answer
~40 sThat object is compiler output, not a broken package. The source was authored with `export default someFunction`, and a tool like Babel or the TypeScript compiler lowered it to CommonJS by writing `exports.default = someFunction` and defining `__esModule: true` on the same exports object. `require()` hands back that whole object verbatim — it has no idea one of the keys is meant to be "the" export — so `pkg` is the container and `pkg.default` is the function. The fix at the call site is `pkg.default(...)`, or `const fn = require('some-package').default`. Before hard-coding that, check whether the package also ships a real ESM entry point you could `import` instead, because reaching through `.default` from CommonJS is a symptom of crossing the module-system boundary, not a normal API.
go deeper
Be able to read the returned object and say plainly that the callable sits at pkg.default because the package was compiled from ES module source. Show that you inspected the value rather than guessing.
Explain the encoding: ESM has a dedicated default slot, CommonJS has only module.exports, so compilers store the default under a default property and stamp a non-enumerable __esModule marker alongside it.
Explain why the same package behaves differently in built and unbuilt files in one repository, and argue for consuming the package's ESM entry point instead of hard-coding a reach through .default that depends on build output.
Frame it as a boundary cost: every crossing between module systems needs a convention that no specification guarantees. Be ready to say where your codebase draws the line and how you keep interop shims from spreading through application code.
## What you are actually holding `require()` in CommonJS returns exactly one thing: whatever the loaded module assigned to `module.exports`. There is no unwrapping step, no notion of "the default", no magic. So when the returned value is an object with `default` and `__esModule` keys, that is literally what the module assigned. ```js const pkg = require('some-package'); console.log(typeof pkg); // 'object' console.log(typeof pkg.default); // 'function' console.log(pkg.__esModule); // true ``` The `TypeError: pkg is not a function` is the honest consequence: you called an object. ## Where the shape comes from ES modules have a dedicated `default` export slot in the language. CommonJS does not — it has one mutable `module.exports` value and nothing else. When a compiler lowers ESM source to CommonJS, it has to encode that extra slot somewhere, and the universal convention is a property literally named `default`. So source that looked like this: ```js export default function greet(name) { return `hello ${name}`; } export const VERSION = '1.0.0'; ``` comes out roughly like this: ```js 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.VERSION = '1.0.0'; exports.default = greet; function greet(name) { return `hello ${name}`; } ``` Named exports become ordinary properties, which is why `pkg.VERSION` reads naturally. The default export becomes `pkg.default`, which does not. ## The __esModule marker `__esModule: true` is a flag the compiler stamps on the exports object to say "this CommonJS object is really a lowered ES module; the `default` key is a default export, not an ordinary property called default." It is a tooling convention — it appears in no ECMAScript specification and Node's own module loader does not consult it. It exists so that *other* compiled code can tell the difference between a module that genuinely has a default export and a plain hand-written CommonJS module that happens to be a function. Note how it is defined: `Object.defineProperty` with no `enumerable: true`, so the marker is non-enumerable and will not show up in `Object.keys(pkg)` or in a spread. If you inspect the object in a debugger you will still see it, which is often the first clue. ## Why nothing unwraps it for you In plain CommonJS there is no interop layer running on your behalf. Compiled code that *imports* gets a helper injected by its own compiler; code you wrote by hand with `require()` gets nothing. So the asymmetry is: - Compiled importer, compiled package: the helper checks `__esModule`, sees `true`, and reads `.default` for you. It just works. - Hand-written `require()`, compiled package: you are the interop layer. You must write `.default`. That is why the same package can appear to work fine in one file and blow up in another within the same repository — one file went through a build step and the other did not. ## What to do about it First, confirm the diagnosis rather than sprinkling `.default` defensively: ```js const mod = require('some-package'); const fn = mod && mod.__esModule ? mod.default : mod; ``` That two-line check is exactly what a compiler-emitted interop helper does, and writing it out makes the intent obvious to the next reader. Second, check the package's published entry points. A package that ships both a CommonJS build and an ES module build is usually better consumed with `import` from ESM code, where the default export arrives as a real default export and no `.default` is needed. Reaching through `.default` is a boundary artefact; it is fine as a local fix but it is not the package's intended public API, and a future release that changes its build output can break the line silently. Third, be aware of the mirror-image trap. Because Node's ESM loader exposes a CommonJS module's entire `module.exports` as the default export, importing this same compiled package from native ESM gives you the container object again — so `import pkg from 'some-package'` binds `{ __esModule: true, default: fn }` and the function is at `pkg.default`. Bundlers, by contrast, typically honour the `__esModule` marker and hand you the inner function. Same package, same import statement, different value depending on who loaded it — which is precisely why interviewers like this question.
- Why doesn't `Object.keys(pkg)` show the `__esModule` property?Compilers define it with `Object.defineProperty(exports, '__esModule', { value: true })`, and a descriptor written that way defaults to `enumerable: false`. Non-enumerable properties are skipped by `Object.keys`, `for...in`, `JSON.stringify` and object spread. It is deliberate: the marker is metadata for interop helpers, and making it enumerable would leak a fake export into anything that copies or serialises the exports object.
- The same package works without `.default` in a file that goes through your build step. Why the inconsistency?The built file isn't running your `import` statement directly — its compiler rewrote the import into a `require()` plus an interop helper that checks `__esModule` and reads `.default` for you. The hand-written `require()` gets no helper, so you do that unwrapping yourself. It's the same package; the difference is whether a compiler sat between you and it.
- Is writing `require('pkg').default` a safe long-term fix?It works, but it depends on the package's build output rather than its documented API. If the maintainer switches bundlers, publishes a native CommonJS entry, or changes to a named export, the line breaks with a confusing `undefined is not a function`. Prefer importing the package's ESM entry point from ESM code, and if you must interop, write the `__esModule` check explicitly so the intent survives.
saying these in an interview costs you the question
- Concludes the package is corrupt and reinstalls it
- Thinks require() automatically unwraps a default export
- Believes Node adds the __esModule marker itself
- Says default is a reserved key in CommonJS
- Adds .default everywhere without checking the shape