How does export = differ from export default in TypeScript declarations?
It tests CommonJS to ES module interop in TypeScript. export = maps to the entire module.exports object and must be imported with import = require() or ES interop, while export default creates a named binding. A red flag is claiming they are interchangeable.
What's really being asked
Whether you understand that TypeScript's type system must model two different module systems faithfully. CommonJS uses module.exports = to reassign the entire exported object, while ES modules use a default export binding. TypeScript needs export = to represent the former because export default describes a different structural shape on the import side.
The full answer
First, the mechanical difference: export = corresponds to the whole module.exports assignment, so it exports exactly one entity that can be a function, object, or class. Second, the import-side contract: consumers must use import foo = require('foo') or enable esModuleInterop and allowSyntheticDefaultImports to bring it in as a default. Third, why export default is wrong for legacy CommonJS: it creates a named export called default that does not exist on the actual module.exports object unless the runtime or compiler synthesizes one. Fourth, the compiler flag implications: without esModuleInterop, writing export default in a declaration file for a classic module.exports = library will cause type errors on import.
The mistakes people make
Claiming that export default and export = are interchangeable in declaration files. Stating that require() always returns the default export. Forgetting that export = prevents named exports alongside it in the same module, whereas export default can coexist with other named exports. Suggesting that modern ES module syntax alone is sufficient without mentioning the interop flags.
What usually comes next
How would you write the declaration file if the library also attaches properties to the exported function? When should you use export as namespace Foo versus export =? How does Node16 module resolution change the way these declarations are consumed? What happens if the consumer uses a named import like import { foo } with an export = declaration?
A concrete example
Suppose a CommonJS library does module.exports = function greet(name) { return 'Hello ' + name; }. The correct .d.ts file declares function greet(name: string): string; then export = greet;. A consumer using classic TypeScript must write import greet = require('my-lib');. If they instead write import greet from 'my-lib'; it only compiles when esModuleInterop is true because the compiler injects a synthetic default. Writing export default function greet... in the declaration file would be incorrect because it tells TypeScript there is a property literally named default on the exports, which the original CommonJS code does not create.
Interview question
When authoring a .d.ts file for a legacy CommonJS library that does module.exports = fn, why is export = preferred over export default?
- a.The two are interchangeable whenever esModuleInterop is enabled
- b.export default forces consumers to use import = require() for correct type inference
- c.export = permits additional named exports alongside it, unlike export default
- d.export = models the entire module.exports assignment, while export default implies a runtime property named default that the library does not createCorrect
Why? this is the answer
export = represents the whole module.exports object, while export default asserts a property literally named default that the CommonJS code never creates. Option A is a common misconception: the two are never interchangeable in declaration files because they describe different runtime shapes, even if interop flags paper over the import syntax.
Just read this? Test yourself on what you have been reading.
- #typescript
- #commonjs
- #declaration-files
- #modules
- #interop
Put your scrolling time to good use
Learn one idea, try a quiz and save useful cards for revision. Tezvyn makes it easy to learn and stay current in your tech field, a few minutes at a time.
The iPhone app is on the way
We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.
Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.
We are hiring for this. Open roles that interview on typescript — each one lists the topics its interview covers.
See open roles