Web Fundamentals

JavaScript Module Systems: CJS vs ESM vs UMD

mediumWeb Fundamentals

JavaScript Module Systems: CJS vs ESM vs UMD

Learn the interview-ready mental model, practical trade-offs, and production patterns for this web fundamentals topic.

Topic content

TL;DRESM (static, tree-shakable) is the modern standard • CJS (runtime, dynamic) is legacy Node • UMD is for broad legacy compatibility
Very High Signal
Google
Meta
Agoda
Meesho
30-Second Answerstart every interview with this

JavaScript has evolved through multiple module systems. CommonJS (CJS) uses synchronous require() and is runtime-based. ES Modules (ESM) use static import/export syntax, enabling tree-shaking and better optimization. UMD is a legacy wrapper that works across environments. Understanding static vs runtime resolution is the key differentiator for performance and tooling.

CJS containers are opened and inspected only when the ship arrives at runtime (dynamic). ESM containers are scanned and optimized at the port (build time) with clear labels, allowing removal of unused cargo (tree-shaking). UMD is an old multi-format shipping crate that works everywhere but is bulky.

Author Code
Resolve Dependencies
Bundler (Tree-shaking + Optimization)
Runtime Execution (CJS vs ESM semantics)

1CommonJS (CJS)

Legacy Node.js module system. Uses synchronous require(). Modules are loaded and executed at runtime. Dynamic imports are possible but prevents many build-time optimizations.

math.cjsjs
const add = (a, b) => a + b;
module.exports = { add };

2ES Modules (ESM)

Modern standard. Uses static import/export. Enables tree-shaking, scope hoisting, and top-level await. Native support in modern browsers and Node (with type: module).

math.mjsjs
export const add = (a, b) => a + b;

export default { add };

3UMD (Universal Module Definition)

Legacy pattern designed to work in browser, Node, and AMD environments. Usually generated by bundlers for library distribution. Larger output size and rarely used in modern apps.

umd-example.jsjs
(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([], factory);
  } else if (typeof module === 'object' && module.exports) {
    module.exports = factory();
  } else {
    root.myLib = factory();
  }
}(typeof self !== 'undefined' ? self : this, function() {
  return { /* lib */ };
}));

4Bundler Role & Interop

Bundlers (Webpack, Vite, Rollup) resolve the module graph at build time. ESM enables powerful optimizations. Interop between CJS and ESM can be tricky — default exports from CJS become .default in ESM.

Key Takeaways
  • ESM is the modern standard — use it by default
  • CJS is runtime-based and blocks tree-shaking
  • Static analysis in ESM enables powerful bundler optimizations
  • CJS ↔ ESM interop requires care (especially default exports)
  • UMD is mostly legacy for CDN library distribution