TL;DR:

  • pnpm is the best default for most teams in 2026: it’s faster than npm, uses significantly less disk space through content-addressable storage, and its strict node_modules layout catches phantom dependency bugs that npm silently allows
  • Yarn Berry (v4+) is the right choice if you want Plug’n’Play (zero-installs, no node_modules) and are willing to configure editor tooling for it
  • npm remains the safe default if you’re minimising toolchain complexity — it ships with Node.js, every tutorial assumes it, and it’s plenty good enough for small projects

Every JavaScript project needs a package manager. For most of Node.js history the choice was npm by default and Yarn as the alternative. pnpm has now been around long enough, and good enough, that it’s become the serious contender. Here’s how they compare on the things that actually matter day to day.

Install Speed

pnpm wins on install speed in most scenarios, particularly on repeat installs. The reason is its content-addressable store: packages are stored once on disk in a global store and hard-linked into each project’s node_modules. Installing the same version of React in five projects only stores it once.

Yarn Berry with PnP is comparable, sometimes faster, because it skips the node_modules directory entirely and resolves packages from a global cache.

npm has improved substantially since v7 — its lockfile format (package-lock.json) improved, and install speed is no longer embarrassing. For a small project with clean cache it’s similar to pnpm. Where pnpm pulls ahead is large monorepos with many packages and frequent installs on CI.

On CI, all three support installation from lockfile (npm ci, yarn install --frozen-lockfile, pnpm install --frozen-lockfile), which is faster than resolution from scratch.

Disk Usage

pnpm is dramatically more efficient. Rather than duplicating dependencies across projects, it uses hard links to a global content-addressable store. If you have 20 projects all using lodash 4.17.21, there’s one copy on disk, with hard links in each project’s node_modules.

On a developer machine with multiple projects, pnpm can reduce total node_modules disk usage by 50-80%. This matters on machines with SSDs measured in hundreds of gigabytes and monorepos that would otherwise hold multiple copies of React, TypeScript toolchains, and similar large dependencies.

npm and Yarn Classic both duplicate dependencies per project. Yarn Berry with PnP stores a zip cache without node_modules, which is also compact but works differently.

Monorepo Workspace Support

All three have workspace support, but pnpm’s is arguably the best-designed. pnpm workspaces use a pnpm-workspace.yaml file and support cross-package dependencies with workspace:* protocol, which means packages reference each other by local path during development and by version when published.

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'

Yarn Berry workspaces are similar and mature, with good tooling for constraint enforcement (ensuring all packages use consistent dependency versions).

npm workspaces work, but they’ve historically had some edge cases around hoisting and nested workspace linking that pnpm and Yarn handle more cleanly. For large monorepos, most teams choose pnpm or Yarn.

Dependency Resolution Strictness

This is where pnpm’s design decision has the most practical impact. npm and Yarn Classic hoist all dependencies to the top-level node_modules, meaning a package can require() another package it didn’t declare as a direct dependency — a “phantom dependency.” This is a subtle bug source: your code works fine in development but breaks when the package that was silently providing the phantom dependency changes its own dependency tree.

pnpm creates a strict node_modules structure where packages can only access their direct dependencies. This means:

  • You can’t accidentally import a transitive dependency without declaring it
  • Your package.json dependencies list is accurate and complete
  • Fewer “works on my machine but not in CI” mysteries

The downside: some older or poorly maintained packages have phantom dependency bugs that work fine with npm but fail with pnpm’s strict mode. When switching an existing project to pnpm, you may hit a few of these. shamefully-hoist=true in .npmrc can work around them temporarily while you investigate.

Plug’n’Play: Yarn Berry’s Distinctive Feature

Yarn Berry (v2+, now at v4) introduced Plug’n’Play as an alternative to node_modules entirely. Instead of populating a node_modules directory, Yarn Berry writes a .pnp.cjs file that patches Node’s module resolution to load packages from a global zip cache.

The benefits:

  • No node_modules directory — faster git status, fewer files to deal with
  • Zero-installs: commit the .yarn cache to git and CI never needs to run install
  • Strict by design: packages can only access their declared dependencies

The cost:

  • Editor setup requires configuration (the Yarn Berry SDKs for VS Code, IntelliJ)
  • Some tools that expect node_modules don’t work without additional config
  • Zero-installs means your git repo contains the package cache, which can be large

For teams already using Yarn and willing to invest in the setup, PnP is genuinely better. For teams starting fresh, pnpm achieves most of the benefits (strict dependencies, better disk usage) with less configuration overhead.

The Corepack Complication

Node.js 14.19+ ships with Corepack, a tool for managing package manager versions per project. You specify the package manager in package.json:

{
  "packageManager": "pnpm@9.15.0"
}

And corepack enable makes it so that running pnpm in the project uses exactly that version. This is valuable for teams because it means everyone, and CI, uses the same package manager version without manual coordination.

Corepack is not enabled by default in most environments — you need to run corepack enable once per machine. Once enabled, it enforces the packageManager field and shims the correct binary. Worth setting up if your team is on pnpm or Yarn Berry and you want version consistency.

Which to Choose

Start a new project or team from scratch: pnpm. It’s faster, saves disk space, and its strictness makes your dependency tree more accurate. Install time: npm install -g pnpm, or enable via Corepack.

Large monorepo with complex workspace needs: pnpm or Yarn Berry. Both have mature workspace tooling. Choose pnpm if you want simpler setup; Yarn Berry if you want PnP and zero-installs.

Existing project with npm: Stay with npm unless you have a specific reason to switch. The migration is not complex but it requires testing. A working project on npm is not a reason to change.

Team that’s already on Yarn Classic (v1): Either migrate to Yarn Berry or switch to pnpm. Yarn Classic is in maintenance mode and receives no new features. Yarn Berry is the continuation; pnpm is the alternative.

Solo projects or quick prototypes: npm. It ships with Node.js, every tutorial and documentation example uses npm commands, and the overhead of choosing a different tool outweighs the benefits for small or short-lived projects.

Lockfile Portability

One practical consideration: lockfiles are not interoperable. package-lock.json (npm), yarn.lock (Yarn), and pnpm-lock.yaml (pnpm) are different formats. Once a project has a lockfile committed, switching package managers means regenerating the lockfile, which changes all resolved versions to current. That’s fine for a greenfield project; it’s a meaningful change for a project that has been pinning specific versions intentionally.

If a project has contributors using different package managers (some using npm, some pnpm), you’ll get conflicting lockfiles. Choose one and enforce it — add a preinstall script that checks and errors if the wrong package manager is used, or rely on the packageManager field in Corepack.

Summary Table

FeaturenpmpnpmYarn Berry
Ships with Node.jsYesNoNo
Install speedGoodFastFast (PnP)
Disk usageHighLowLow
Dependency strictnessLooseStrictStrict
Monorepo workspacesGoodExcellentExcellent
node_modulesYesYesOptional (PnP)
Setup complexityLowLowMedium
Ecosystem compatibilityHighestHighVariable