How CommonJS Require Caching Works in JavaScript

In JavaScript’s CommonJS module system, invoking require() loads and evaluates a module only once, subsequently caching its exported value in memory. When require() is called multiple times for the same module within an application, Node.js skips re-reading and re-executing the file, returning the stored reference from the internal cache instead. This mechanism optimizes performance, maintains consistent module state, and resolves potential issues with circular dependencies.

The require.cache Registry

At the core of CommonJS caching is require.cache, an internal JavaScript object exposed by the runtime. Every loaded module is stored as a property on this object, using its fully resolved absolute file path as the key.

When you execute require('./myModule'), Node.js follows this step-by-step resolution process:

  1. Path Resolution: It resolves the relative path to an absolute path (e.g., /app/src/myModule.js).
  2. Cache Lookup: It checks if require.cache[resolvedPath] exists.
  3. Execution (Cache Miss): If the key does not exist, the file is read from the disk, wrapped in a module function, and executed. The resulting module.exports object is attached to require.cache[resolvedPath].
  4. Return (Cache Hit): If the key already exists, Node.js bypasses file reading and execution entirely, returning require.cache[resolvedPath].exports.

Implications of CommonJS Caching

1. Modules Act as Singletons

Because the cached object is returned by reference, all files importing the same module share the exact same instance. Any mutations made to exported objects or variables will persist and be visible across all other parts of the application that import that module.

2. Side Effects Run Only Once

If a module contains top-level code (such as console.log() statements, database connection initializations, or timer setups), that code runs exclusively on the first require() call. Subsequent require() statements in other files will not re-trigger those side effects.

3. Handling Circular Dependencies

Caching prevents infinite loops when two modules require each other. If Module A requires Module B, and Module B requires Module A, Module B receives the current (possibly incomplete) copy of Module A’s exports from require.cache rather than causing an endless loading cycle.

Cache Invalidation and Clearing

While caching is automatic, you can manually bust or invalidate the cache by deleting the entry from require.cache:

const modulePath = require.resolve('./myModule');
delete require.cache[modulePath];

// The module will now be read and executed again
const freshInstance = require('./myModule');

Manually clearing the cache forces the runtime to re-evaluate the file on the next require() call. However, previously loaded modules that already hold a reference to the old export will continue using the old object reference unless explicitly updated.