node_modules Structure & Hoisting

Understand why npm flattens dependencies, the danger of phantom dependencies, and how pnpm fixes it with symlinks.

Package Managers4 min readConcept 8 of 24

The problem with nested dependencies

In early versions of npm, node_modules was strictly nested. If your app installed express, and express needed accepts, npm placed accepts inside node_modules/express/node_modules/accepts.

This created massive duplication (downloading the same package 50 times) and caused Windows to frequently crash because the file paths exceeded the 260-character limit.

The Flattening (Hoisting) Solution

To fix this, npm 3 introduced **hoisting**. When you install a dependency, npm tries to 'flatten' the tree by lifting all sub-dependencies up to the root node_modules folder.

This solves the duplication and path length issues, but creates a dangerous new problem: **Phantom Dependencies**.

Phantom Dependencies

Because sub-dependencies like lodash are hoisted to the root node_modules, your code can accidentally import 'lodash' even though you never explicitly listed it in your package.json.

If the top-level package later drops its dependency on lodash, it gets removed from the root, and your app suddenly crashes in production with a Module not found error.

Remember this

  • npm flattens node_modules to save space and avoid long file paths.
  • Hoisting creates 'Phantom Dependencies', allowing you to import packages you didn't explicitly install.
  • pnpm uses symlinks to enforce strict dependency boundaries while remaining flat on disk.

Done with this concept?

Mark it complete to track your progress. No login needed.