What is the difference between ES modules and CommonJS?
CommonJS is the older system, and it is what Node was built on. It is a runtime system. You call the require function, it returns the exports object, and the code runs immediately as it is required. Everything about it is decided at runtime.
ES modules are the standard system. The import and export keywords are static, so the module graph is built before any code runs. The engine knows exactly what each module imports and exports without executing anything, which is what makes tree shaking possible and what lets it refuse to load a module whose import is misspelled until it is actually needed.
Five differences come up in interviews. Imports are hoisted, so you can use an imported binding before the import statement in the file. Exports are live views, so a change to an exported variable is visible to importers without re-importing, whereas the CommonJS exports object is a snapshot. CommonJS can export a value conditionally or in a loop, and ES modules cannot, because the export list is fixed at parse time. And ES modules support a top level await, which lets a module wait for data before it finishes loading.
There is a practical consequence too. CommonJS is synchronous by default, so requiring a heavy module blocks the event loop. ES module loading is asynchronous, so a large graph does not stall the server in the same way.