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:
- Path Resolution: It resolves the relative path to
an absolute path (e.g.,
/app/src/myModule.js). - Cache Lookup: It checks if
require.cache[resolvedPath]exists. - 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.exportsobject is attached torequire.cache[resolvedPath]. - 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.