Skip to main content

module resolution

1. What "module resolution" is

Module resolution is the process Node.js uses to find the physical file (module) named in require() or import.

In other words:

javascript
const express = require('express');

means:

Node.js has to figure out exactly where the express module lives, in core, in node_modules, or at a local path.

2. The general search sequence

When Node.js hits require('something'), it goes through five stages:

  1. Checks whether it's a core (built-in) module;
  2. Checks whether it's a file path (./, ../, /);
  3. Otherwise assumes it's a module from node_modules;
  4. Looks for it in the nearest node_modules, walking up the directory tree;
  5. If nothing is found, throws a MODULE_NOT_FOUND error.

3. Types of modules Node.js can resolve

TypeExampleDescription
Core (built-in)fs, path, os, httpBuilt into Node.js, no install needed
Local files./utils.js, ../config.jsonYour own files
Packages (npm)express, chalk, mongooseLooked up in node_modules
JSON / binaryconfig.json, addon.nodeAuto-parsed or loaded as binary

4. The module resolution algorithm (for CommonJS)

Example:

javascript
const utils = require('./utils');

Node.js does this:

  1. Checks whether './utils' is a path:
  • does it start with ./, ../ or /? Yes.
  1. Turns the path into an absolute one:
javascript
/Users/tim/project/utils
  1. Tries the following, in order:
javascript
utils.js utils.json utils.node utils/index.js utils/index.json utils/index.node
  1. If nothing is found → an error:
javascript
Error: Cannot find module './utils'

5. The algorithm for node_modules packages

Example:

javascript
const express = require('express');

Node.js:

  1. Looks for a built-in module named express → no.
  2. Looks for node_modules/express in the current folder.
  3. If not found, walks up the tree:
javascript
/Users/tim/project/node_modules/express /Users/tim/node_modules/express /Users/node_modules/express /node_modules/express
  1. Found it → reads the express/package.json file.
  2. Looks for the "main" field in it:
javascript
{ "name": "express", "main": "index.js" }
  1. Loads /node_modules/express/index.js.

6. Visually

javascript
project/ ├── app.js ├── utils/ │ └── index.js └── node_modules/ └── lodash/ ├── package.json └── lodash.js
javascript
require('./utils'); // → ./utils/index.js require('lodash'); // → ./node_modules/lodash/lodash.js require('fs'); // → a built-in Node.js module

7. How Node.js chooses between .js, .json and .node

Node tries extensions in this order:

  1. .js, a plain JavaScript file;
  2. .json, auto-parsed into an object;
  3. .node, a native binary module (C++).

Example:

javascript
require('./config'); // tries config.js → config.json → config.node

8. Module resolution for ES Modules (ESM)

For ESM (import/export), the algorithm is different, and stricter:

  • The exact extension must be given (.js, .mjs, .json);
  • Folders with an index.js aren't searched for automatically;
  • __dirname and __filename don't exist;
  • "exports" in package.json defines the available paths.

Example:

javascript
import utils from './utils/index.js'; // the extension is required

9. How package.json affects module resolution

Node.js looks at these fields:

FieldPurpose
"main"The main entry point for CommonJS
"exports"The modern alternative for ESM / CJS
"type"Sets the module type (commonjs or module)

Example:

javascript
{ "name": "my-lib", "main": "./dist/index.cjs", "exports": { "import": "./dist/index.mjs", "require": "./dist/index.cjs" }, "type": "module" }

Node uses "exports" instead of "main" when it's present, the modern way to control which files can be imported from outside.

10. Caching during module resolution

Once Node.js resolves a module and loads it, the result is stored in require.cache:

javascript
console.log(require.cache);

A repeated require() doesn't re-run the file, it just returns its exports from the cache. This speeds up loading and prevents recursive cycles (circular dependencies).

11. Tools for debugging resolution

To see exactly where a module is being picked up from:

javascript
node -p "require.resolve('express')"

Example output:

javascript
/Users/tim/project/node_modules/express/index.js

Or for a local file:

javascript
node -p "require.resolve('./utils')"

In one sentence

Module resolution is the mechanism Node.js uses to figure out where a module named in require() or import physically lives, checking built-in, local, and node_modules locations until it finds the right file.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.