Module Caching
Node evaluates each module exactly once. The first require (or import) runs the file top to bottom and stores the result; every later request for the same module returns that cached value without re-running the code. This is not just an optimization — it is the mechanism behind the most common way to share state in Node: the module singleton.
The body runs once
setup.js
console.log('setup.js is being evaluated')
module.exports = { ready: true }app.js
const a = require('./setup')
const b = require('./setup')
const c = require('./setup')
console.log(a === b, b === c) // true true — same object every timesetup.js is being evaluated true true
The log appears once despite three require calls. After the first, Node returns the cached module.exports instantly.
The cache key is the resolved path
Modules are cached by their absolute resolved filename, not the string you typed. So require('./utils') and require('./utils.js') from the same folder hit the same cache entry — but the same package resolved at two different node_modules depths counts as two separate modules:
const Module = require('node:module')
console.log(Object.keys(require.cache))
// [ '/app/app.js', '/app/setup.js', ... ] — keyed by absolute pathThe singleton pattern (the good use)
Because a module is evaluated once and shared, anything you create at module scope becomes a process-wide singleton — the idiomatic, global-free way to share a database pool, a config object, or a client:
db.js — created once, shared everywhere
import { Pool } from 'pg'
// This Pool is constructed the first time db.js is imported,
// then the SAME instance is returned to every other importer.
export const pool = new Pool({ connectionString: process.env.DATABASE_URL })// users.js and orders.js both import './db.js'
import { pool } from './db.js' // identical shared pool — no global neededCaching and errors
Busting the cache (CommonJS)
Occasionally — usually in tooling or tests — you want to force a fresh load. In CommonJS you can delete the entry from require.cache:
delete require.cache[require.resolve('./config')]
const fresh = require('./config') // re-evaluated from diskKey takeaways
A module evaluates once; later imports get the cached exports.
The cache key is the absolute resolved path — beware duplicate package copies.
Module scope = a free, explicit singleton for shared resources.
Modules that throw are evicted and will re-run; keep top-level code idempotent.
Cache-busting works in CJS via
require.cache; ESM has no user-facing equivalent.