DevPrep
  • Interview Prep
  • Projects
  • Resources
  • Pricing
  • About Us
Submit Question
DevPrep
  • Pricing
  • About Us
Submit Question
  1. Home
  2. Articles
  3. Frontend Engineering
  4. AMD, CommonJS, and ES Modules: JavaScript Module Systems Explained
XLinkedInReddit
MediumFrontend Engineering

AMD, CommonJS, and ES Modules: JavaScript Module Systems Explained

D
DevPrep Team
February 9, 2026·3 min read·0
Table of Contents
  • Why Do We Have Multiple Module Systems?
  • CommonJS (CJS)
  • Key Characteristics
  • AMD (Asynchronous Module Definition)
  • ES Modules (ESM)
  • Key Differences from CommonJS
  • Live Bindings vs Value Copies
  • Real Production Issues
  • Best Practices
  • Summary

By Rahul — Google Frontend Engineer

Why Do We Have Multiple Module Systems?

JavaScript was born without a module system. For years, we used global variables and script tags. Then different communities created their own solutions. Understanding the history helps you understand why your bundler does what it does.

CommonJS (CJS)

Created for Node.js. Synchronous loading — works fine on a server where files are on the local disk.

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

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

Key Characteristics

  • Synchronous: require() blocks until the module is loaded
  • Dynamic: You can require inside if statements
  • Copies values: When you import a primitive, you get a copy, not a live binding
  • Runs on evaluation: The module code runs when first required, then result is cached

AMD (Asynchronous Module Definition)

Created for browsers where synchronous loading blocks rendering. RequireJS was the popular implementation.

// define a module
define('math', [], function() {
  return {
    add: function(a, b) { return a + b; }
  };
});

// use a module
require(['math'], function(math) {
  console.log(math.add(1, 2));
});

AMD is mostly dead now. You will only see it in legacy projects. If you encounter it, your job is to migrate to ES modules.

ES Modules (ESM)

The official standard. Supported natively in all modern browsers and Node.js (since v12 with .mjs or "type": "module").

// math.js
export const add = (a, b) => a + b;
export default function multiply(a, b) { return a * b; }

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

Key Differences from CommonJS

  • Static structure: Imports must be at the top level. This enables tree-shaking
  • Live bindings: If the exporting module changes a value, the importing module sees the change
  • Asynchronous: Browser can fetch modules in parallel
  • Strict mode by default

Live Bindings vs Value Copies

// CommonJS — copies value
// counter.js
let count = 0;
module.exports = { count, increment: () => ++count };

// app.js
const { count, increment } = require('./counter');
increment();
console.log(count); // Still 0! It is a copy

// ES Modules — live binding
// counter.mjs
export let count = 0;
export const increment = () => ++count;

// app.mjs
import { count, increment } from './counter.mjs';
increment();
console.log(count); // 1! Live binding

Real Production Issues

  • Dual package hazard: A library ships both CJS and ESM. If your app loads both versions, singletons break. I saw this with a state management library where the CJS and ESM versions had separate state stores
  • Default export confusion: CJS module.exports = fn becomes { default: fn } when imported as ESM. This causes fn is not a function errors
  • .mjs vs .js: Node treats .js as CJS unless package.json has "type": "module". Getting this wrong breaks everything silently

Best Practices

  • Use ES modules for all new code
  • If you publish a library, ship both CJS and ESM using the exports field in package.json
  • Use dynamic import() for code splitting, not require()
  • Set "type": "module" in new Node.js projects

Summary

CommonJS was for Node.js, AMD was for browsers, ES Modules is the standard that replaced both. Use ESM. Understand CJS because you will encounter it in legacy code and Node.js libraries.

Related Articles

MediumFrontend Engineering

System Design #12: Design a Multi-Step Form Wizard

7 min read
MediumFrontend Engineering

Mastering Senior-Level JavaScript Interview Concepts

2 min read
MediumFrontend Engineering

System Design #9: Design a Collaborative Text Editor

9 min read

Comments (0)

Sign in to leave a comment.

No comments yet. Be the first to comment.

Table of Contents

  • Why Do We Have Multiple Module Systems?
  • CommonJS (CJS)
  • Key Characteristics
  • AMD (Asynchronous Module Definition)
  • ES Modules (ESM)
  • Key Differences from CommonJS
  • Live Bindings vs Value Copies
  • Real Production Issues
  • Best Practices
  • Summary

Series

View all Frontend Engineering articles →

Practice

  • JavaScript
  • DSA
  • Machine Coding
  • System Design

Resources

  • Learning Tracks
  • Articles
  • Roadmaps
  • Compare Concepts
  • Glossary
  • Developer Tools
  • All Questions

Company

  • About
  • Pricing

Legal

  • Privacy Policy
  • Terms of Service
DevPrep

© 2026 DevPrep. All rights reserved.