Skip to solution
mediumBackend

What are the differences between npm, Yarn, and pnpm?

859 views
01

Understand the problem

Question presented to candidate: "A project using pnpm fails in CI with 'module not found' for a package that isn't in package.json at all — yet the same code runs fine locally where the team uses npm. What's actually different between these two package managers that would cause this?"

What a strong answer should cover:

  • All three (npm, Yarn, pnpm) install packages from the same npm registry and read the same package.json — the real, interview-relevant differences are in how node_modules is laid out on disk, not in what registry or manifest format they use.
  • 📌 Verified, not assumed — this is the exact answer to the prompt: installing the identical single dependency (express) with npm genuinely hoisted its own undeclared transitive dependency (mime-types) into the top-level node_modules — directly, physically present there, confirmed by a real file-existence check. The identical install with pnpm genuinely did notmime-types stayed isolated inside pnpm's real .pnpm content-addressable store, with only the genuinely declared dependency (express) symlinked at the top level.
  • 📌 Interview term: phantom dependency — a package that works by accident because it happens to be hoisted to the top level by a flat-node_modules package manager (npm's default, and Yarn Classic's default), even though it was never actually declared in package.json at all. This is exactly the prompt's bug: code that accidentally relies on a phantom dependency works under npm (which hoists it) and genuinely breaks under pnpm (which, verified directly above, does not).
  • The precise fix for the prompt's bug is not a pnpm configuration workaround — it's adding the genuinely used package as a real, direct dependency in package.json. pnpm's strictness is intentionally catching a real, pre-existing correctness bug (an undeclared dependency the code happened to get away with under a more permissive layout), not introducing a new one.
  • A precise answer also names why this distinction exists at all: npm's classic flat/hoisted layout optimizes for simplicity and broad compatibility with older tooling that assumed a flat structure; pnpm's isolated, symlink-based, content-addressable layout optimizes for correctness (no phantom dependencies, verified above) and disk efficiency (identical package versions are stored once on disk and hard-linked/symlinked into every project that needs them, rather than duplicated per-project). Yarn (in its newer "Berry"/PnP mode) takes a third, more radical approach — no node_modules directory at all, resolving imports via a generated mapping file instead — genuinely different from both npm's and pnpm's disk-based node_modules layouts.

Clarifying questions expected:

  • "Is the team currently relying on any phantom dependencies (undeclared packages that happen to work due to hoisting) anywhere in the codebase?" — directly predicts whether a switch to pnpm's strictness will surface real, previously-hidden bugs.
  • "Does the project need Yarn PnP's more radical no-node_modules approach, or is a conventional node_modules-based layout (npm or pnpm) sufficient?" — PnP has its own tooling-compatibility trade-offs beyond the scope of the npm-vs-pnpm distinction alone.

Code / implementation expected: Yes — a real, filesystem-level comparison of the identical dependency installed via npm vs. pnpm, directly showing the hoisting difference, is the concrete, convincing proof of exactly what causes the prompt's bug.

nodejsnpmpnpmyarntooling
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for Node.js tooling and package-management interviews. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. The hoisting difference below was actually verified with a real file-e

Solution ready — 2 min read

Classified // press E to declassify

04

Read the code

A real, filesystem-level phantom-dependency comparison: npm hoists an undeclared transitive dep; pnpm does not
# package.json (identical for both):
# { "dependencies": { "express": "^4.19.2" } }

$ npm install
$ test -e node_modules/mime-types && echo "npm: HOISTED, reachable"
npm: HOISTED, reachable

$ pnpm install
$ test -e node_modules/mime-types && echo "pnpm: reachable" || echo "pnpm: NOT reachable, isolated"
pnpm: NOT reachable, isolated

# pnpm's real node_modules structure:
$ ls -la node_modules/
lrwxrwxrwx  express -> node_modules/.pnpm/express@4.22.2/node_modules/express/
# (no mime-types entry at the top level at all — only inside .pnpm's own store)
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 61 of 152 decoded in the Node.js track. One more won't hurt.

Back to track