CommonJS vs ES Modules

JavaScript

The short answer

CommonJS and ES Modules are two different ways to organize and share code between JavaScript files. CommonJS uses require() and module.exports, and it was created for Node.js. ES Modules use import and export, and it is the official standard built into the JavaScript language. ES Modules are the future, but CommonJS is still widely used in Node.js.

CommonJS

CommonJS was created for Node.js because JavaScript did not have a module system when Node.js was first built.

Exporting:

// math.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };

Importing:

// app.js
const { add, subtract } = require('./math');
console.log(add(2, 3)); // 5

Key things about CommonJS:

  • require() is synchronous — it loads the file immediately and blocks execution until done
  • You can use require() anywhere in your code, even inside if statements
  • It runs at runtime — the module is loaded when the code reaches require()

ES Modules

ES Modules (ESM) were introduced in ES6 (2015) as the official module system for JavaScript.

Exporting:

// math.js
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// or use default export
export default function multiply(a, b) {
return a * b;
}

Importing:

// app.js
import multiply, { add, subtract } from './math.js';
console.log(add(2, 3)); // 5

Key things about ES Modules:

  • import statements must be at the top of the file — you cannot put them inside if statements
  • They are asynchronous — the browser or runtime can load them in parallel
  • They are analyzed at compile time (statically), before the code runs
  • They support tree shaking — bundlers can remove unused exports

The key differences

1. Syntax:

// CommonJS
const fs = require('fs');
module.exports = { myFunction };
// ES Modules
import fs from 'fs';
export { myFunction };

2. Loading behavior:

CommonJS loads modules synchronously. When you require() a file, execution stops until the file is fully loaded. ES Modules are loaded asynchronously and analyzed statically before execution.

3. When they run:

CommonJS — at runtime. The require() call is evaluated when the code reaches that line. You can compute the module path dynamically:

// This works in CommonJS
const module = require(`./${condition ? 'a' : 'b'}`);

ES Modules — at compile time. Imports are analyzed before any code runs. You cannot compute the import path:

// This does NOT work with static import
import module from `./${condition ? 'a' : 'b'}`; // SyntaxError

For dynamic imports in ES Modules, you use the import() function:

const module = await import(`./modules/${name}.js`);

4. Tree shaking:

This is one of the biggest advantages of ES Modules. Because imports are static, bundlers like Webpack and Vite can analyze which exports are actually used and remove the unused ones. This reduces your bundle size.

CommonJS cannot be tree-shaken because require() is dynamic — the bundler cannot know at build time which parts of a module are used.

5. Default exports:

// CommonJS — module.exports is the default
module.exports = function () {};
const myFunc = require('./myFunc');
// ES Modules — explicit default export
export default function () {}
import myFunc from './myFunc';
// ES Modules also have named exports
export function helper() {}
import { helper } from './myFunc';

What should you use?

  • Frontend code — always ES Modules. Every modern bundler (Webpack, Vite, Rollup) and every browser supports them.
  • Node.js — ES Modules are now fully supported. New projects should use ESM. You can either use .mjs file extension or add "type": "module" to your package.json.
  • Older Node.js projects — you will still see CommonJS everywhere. Many npm packages still use CommonJS.

Common Pitfalls

A common mistake is mixing CommonJS and ES Modules in the same project without understanding the rules. You cannot use require() inside an ES Module file. And you cannot use top-level import in a CommonJS file without changing the file extension or package configuration. This often causes confusing errors.

Interview Tip

Focus on three things: the syntax difference, the loading behavior (sync vs async), and tree shaking. The tree shaking point is especially important because it directly affects application performance. If you can explain why ES Modules enable tree shaking and CommonJS does not, that shows a strong understanding.

Why interviewers ask this

Module systems are part of your everyday workflow as a frontend engineer. Interviewers ask this to see if you understand how code is organized in JavaScript projects, if you know the difference between the two systems, and if you can explain practical implications like tree shaking. It also shows if you have worked with both Node.js and frontend build tools.