Master Node.js Module System: Loading, Caching, and CommonJS Pitfalls

This article explains Node.js's CommonJS module system, covering how require loads modules, the role of module.exports versus exports, module classification, loading order, caching behavior, circular dependencies, and provides practical code examples for interview preparation.

Node Underground
Node Underground
Node Underground
Master Node.js Module System: Loading, Caching, and CommonJS Pitfalls

Interview Guide

require

loading mechanism? (see module loading mechanism)

Difference between module.exports and exports (object reference relationship)

What happens when a.js and b.js reference each other? (circular dependency problem 1)

Will the undeclared variable in module a be printed in b.js? (circular dependency problem 2)

Is the require process synchronous or asynchronous? (see file module loading)

Module Classification

System Modules

C/C++ built‑in modules, used for native calls.

Native modules such as http, buffer, fs that internally rely on built‑in C/C++ modules.

Third‑Party Modules

Modules not bundled with Node.js. They include path‑based file modules (starting with ., .., or /) and custom modules like express, koa, moment.js.

JavaScript module example: hello.js JSON module example: hello.json C/C++ compiled module example:

hello.node

Directory Structure

├── benchmark           // Node.js performance test code
├── deps                // Node.js dependencies
├── doc                 // Documentation
├── lib                 // Exposed JS modules
├── src                 // C/C++ source files (built‑in modules)
├── test                // Unit tests
├── tools               // Build tools
├── vcbuild.bat         // Windows makefile
├── node.gyp            // node‑gyp configuration
...

Module Loading Mechanism

When a module is required, Node.js typically follows three steps: path resolution, file locating, and compiling/executing.

Loading order based on module type:

System cache : If the module has been executed before, its cached export is returned.

System modules : Native modules are loaded directly after cache check, bypassing path resolution and file locating.

File modules : Modules whose paths start with ., .., or / are loaded next. If no extension is provided, Node.js tries .js, .json, then .node synchronously.

Directory as module : If a directory is found, Node looks for a package.json and uses its main field; otherwise it throws Cannot find module.

node_modules lookup : If the module is still not found, Node searches parent directories up to the filesystem root.

Module Cache Location

After a module is loaded, its exports are stored in require.cache. The following example demonstrates how to inspect the cache.

test-module.js

module.exports = {
  a: 1,
  test: () => {}
};

test.js

require('./test-module.js');
console.log(require.cache);

The output shows each cached module’s filename, path, and exported data.

Module Circular References

Problem 1

What happens when a.js and b.js require each other? Does it cause an infinite loop?
// a.js
console.log('a module start');
exports.test = 1;
undeclaredVariable = 'a module undeclared';
const b = require('./b');
console.log('a module finished: b.test =', b.test);
// b.js
console.log('b module start');
exports.test = 2;
const a = require('./a');
console.log('undeclaredVariable:', undeclaredVariable);
console.log('b module finished: a.test =', a.test);

Running node a.js loads b.js while a.js is still executing. Node provides b with an unfinished copy of a 's exports, preventing a true dead‑loop. After b finishes, its completed exports are assigned to a.

Problem 2

Will the undeclared variable from a be printed in b.js ?

Because undeclaredVariable becomes a global variable, it is accessible from other modules.

Object Reference Relationship

Most interviewers ask: what is the difference between module.exports and exports ?
exports

is a shortcut to module.exports: const exports = module.exports; However, reassigning exports changes its reference and breaks the link. The correct way is to add properties to exports or assign directly to module.exports:

// Correct
module.exports = { a: 1, b: 2 };
// Incorrect – will result in <code>undefined</code>
exports = { a: 1, b: 2 };

Understanding this relationship helps avoid common pitfalls when exporting from Node.js modules.

Node.jsmodule loadingCommonJSExportscircular dependenciesmodule.exports
Node Underground
Written by

Node Underground

No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.