Back
Web Fundamentals

JavaScript Module Systems: CJS vs ESM vs UMD

Web Fundamentals
Build & Deployment: Monorepo, CI/CD, Strategies & Release SafetyState Management: Choosing the Right SolutionRedux: Predictable State Container (RTK + RTK Query)React Query (TanStack Query): Server State CachingData Fetching Patterns: REST, GraphQL, tRPC & Real-timeGraphQL Fundamentals for Frontend: Shape, Caching, and TradeoffsgRPC-Web Fundamentals: Browser Constraints and Proxy ModelCaching Strategies: Client, Server & EdgeData Normalization: Organizing State for PerformanceAPI Design Best Practices: Pagination, Errors, Versioning & Type SafetyAPI Versioning Strategies for Frontend CompatibilityPagination: Offset vs Cursor-BasedRate Limiting & API Resilience: Retries, Backoff, Jitter, IdempotencyHow Frontend Developers Can Handle Millions of API Requests Without Crashing EverythingBrowser Storage: Cookies, SessionStorage, LocalStorage, IndexedDBReal-time Communication: WebSockets, SSE & PollingWebRTC: Real-Time Communication in the BrowserCore Web Vitals: LCP, INP & CLSPerformance Optimization Trade-offsCritical Resource Prioritization: Optimize Loading OrderCode Splitting: Optimize Bundle Size with Dynamic ImportsTree Shaking: Eliminate Dead Code from Your BundleLazy Loading: Load Resources On-DemandResource Hints: Preload, Prefetch & PreconnectText Compression: Gzip and BrotliImage & Video Optimization: Modern Formats & TechniquesAdaptive Loading: Optimize for Device & NetworkList Virtualization: Render Large Lists EfficientlyWeb Workers vs Main Thread: Offloading Heavy WorkMemory Leaks in Frontend Apps: Detection & PreventionManaging Third-Party Scripts: Optimization StrategiesHow CDNs Work: Edge Delivery, Caching & PerformanceHTTP Caching Deep Dive: Cache-Control, ETag & RevalidationService Workers & Offline Strategy: Cache First, Network First & Update LifecyclePWA Fundamentals: Manifest, Installability & Offline UXCritical Rendering PathScript Loading: async vs deferEvent Loop: Understanding JavaScript Execution ModelJavaScript Module Systems: CJS vs ESM vs UMDDynamic Module Loading: import() FunctionImport on Interaction: Load When User InteractsImport on Visibility: Lazy Loading with IntersectionObserverBrowser Rendering Pipeline & Layout ThrashingRendering Strategies: CSR vs SSR vs SSG vs ISRStreaming SSR: Progressive HTML StreamingIslands Architecture: Independent Component HydrationReact Server Components: Zero-JS Server RenderingFramework Reactivity: React, Vue, Svelte & SolidHTTP/1.1 vs HTTP/2 vs HTTP/3 (QUIC) for Frontend PerformanceDNS Resolution: Path, TTL, Caching & Frontend ImpactCross-Site Scripting (XSS) AttacksCross-Site Request Forgery (CSRF) AttacksCORS Explained: Cross-Origin Resource SharingCORS Preflight in Practice: Credentials, Simple Requests & MisconfigurationsContent Security Policy (CSP)Why is HTTPS Secure? Understanding TLS/SSLAuthorization Best PracticesCookie Security & Session Hardening: SameSite, HttpOnly, Secure
mediumRendering & Browser Architecture

JavaScript Module Systems: CJS vs ESM vs UMD

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.

PropertyCommonJS (CJS)ES Modules (ESM)UMD
LoadingSynchronousAsync by defaultDepends on environment
BehaviorRuntime resolution (synchronous require)Static analysis (import/export)Universal wrapper for multiple environments
Use CaseLegacy Node.js, server-side packagesModern frontend & Node (type: module)Legacy library distribution (CDN)
Tree ShakingNot possibleFully supportedLimited
Modern StatusStill common in Node ecosystemCurrent standardMostly obsolete

CommonJS (CJS)

Loading

Synchronous

Behavior

Runtime resolution (synchronous require)

Use Case

Legacy Node.js, server-side packages

Tree Shaking

Not possible

Modern Status

Still common in Node ecosystem

ES Modules (ESM)

Loading

Async by default

Behavior

Static analysis (import/export)

Use Case

Modern frontend & Node (type: module)

Tree Shaking

Fully supported

Modern Status

Current standard

UMD

Loading

Depends on environment

Behavior

Universal wrapper for multiple environments

Use Case

Legacy library distribution (CDN)

Tree Shaking

Limited

Modern Status

Mostly obsolete

Common questions

  • ›“What is the difference between CJS and ESM?”
  • ›“Why does tree-shaking only work with ESM?”
  • ›“How do you handle CJS/ESM interop issues?”
  • ›“When would you still use UMD?”

What interviewers look for

  • Deep understanding of static vs runtime module resolution
  • Knowledge of tree-shaking and bundler optimizations
  • Practical interop strategies
  • Modern best practices (ESM-first)

Short answer (60 sec)

ESM uses static imports for tree-shaking and optimization. CJS uses dynamic require() at runtime. UMD is a legacy wrapper for broad compatibility. Today we default to ESM.

Detailed answer (senior level)

The core difference is static (ESM) vs runtime (CJS) resolution. ESM allows bundlers to analyze the dependency graph at build time, enabling tree-shaking and scope hoisting. CJS executes code during require(), limiting optimizations. Interop is tricky because CJS modules export a single object while ESM has named + default exports. Senior engineers prefer ESM and use bundlers to handle legacy CJS packages.

  • Mixing require() and import in the same file
  • Expecting tree-shaking to work with CJS modules
  • Default export confusion when importing CJS into ESM
  • Forgetting to set "type": "module" in Node.js package.json
  • Using UMD for new application code
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
Previous TopicEvent Loop: Understanding JavaScript Execution ModelNext Topic Dynamic Module Loading: import() Function

On this page