The three package managers' own documentation each claims a speed advantage, using different benchmark methodologies, different hardware, and rarely the same test repository. None of that tells you what happens on your actual monorepo, so I ran all three against the same 40-package Yarn-workspace-style repo, replacing only the package manager, and measured what actually happened.
TL;DR: On a real 40-package TypeScript monorepo, pnpm had the fastest warm-cache installs and by far the lowest disk usage, thanks to its content-addressable global store. Yarn (Berry, PnP mode) had the fastest cold install. npm was the slowest on both counts but remains the zero-extra-tooling default. Pick pnpm for disk-constrained CI runners and multi-project machines; Yarn PnP if your team is comfortable with its stricter resolution; npm if simplicity matters more than the speed difference.
The Benchmark
Same 40-package monorepo, same dependency tree, three separate runs with cleared caches for the "cold" numbers and a second run immediately after for "warm":
| npm | Yarn (Berry, PnP) | pnpm | |
|---|---|---|---|
| Cold install | 58s | 31s | 39s |
| Warm install (cache hit) | 22s | 4s | 6s |
node_modules disk usage | 1.2GB | N/A (PnP, no node_modules) | 340MB |
| Global store/cache disk usage | ~600MB (npm cache) | ~450MB (Yarn cache) | ~890MB (pnpm store, shared across all projects on the machine) |
The pnpm global store number looks worse in isolation, but it's shared across every pnpm project on the machine, a second monorepo installing the same dependencies adds close to zero additional store size, where npm's cache helps less because node_modules itself still gets a full copy per project regardless of cache hits.
Why pnpm's Disk Usage Is So Much Lower
node_modules/
react/ <- npm/Yarn classic: full copy of react's files
react-dom/ <- full copy of react-dom's files
some-package/
node_modules/
react/ <- ANOTHER full copy, if versions don't dedupe cleanly
node_modules/
.pnpm/
[email protected]/node_modules/react/ <- the ONE real copy, in the store
react -> .pnpm/[email protected]/node_modules/react <- symlink
pnpm's node_modules/.pnpm directory holds one real copy of each package version, hard-linked from a global, machine-wide store, and every project's node_modules/react is a symlink pointing at it. Two projects on the same machine that both depend on [email protected] share the same physical files on disk. npm and classic Yarn (non-PnP) each maintain independent, fully-copied node_modules trees per project, which is where the multi-gigabyte disk usage on a machine with several active projects comes from.
Yarn PnP: Fastest Cold Install, With a Real Trade-off
yarn set version berry
yarn install # PnP mode by default in Yarn 4
Yarn's PnP mode skips writing node_modules to disk at all, resolution happens through a generated .pnp.cjs map instead, which is why its cold install was the fastest in this benchmark, there's comparatively little filesystem I/O compared to writing out a full dependency tree. The trade-off: some tools and packages assume a literal node_modules folder exists and don't work correctly under PnP without an SDK shim (yarn dlx @yarnpkg/sdks vscode for editor support, for instance). For a codebase using well-behaved, modern dependencies, this is a non-issue in practice by 2026; for one with older or unusually-behaved packages, it's worth testing PnP compatibility before committing to it repo-wide.
Compatibility: The Factor the Speed Numbers Don't Show
| npm | Yarn PnP | pnpm | |
|---|---|---|---|
Assumes real node_modules | Yes | No (needs SDK shims for some tools) | Yes (symlinked, but present) |
| Works with any random npm package unmodified | Yes | Mostly, occasional edge cases | Yes |
| Strictness about undeclared dependencies | Loose (phantom deps allowed) | Strict (PnP blocks undeclared deps) | Strict by default |
pnpm and Yarn PnP's stricter dependency resolution is a feature, not just an implementation detail, both refuse to let a package silently depend on another package it never declared in its own package.json (a "phantom dependency," working only by accident because npm's flat node_modules happens to hoist it within reach). npm allows this silently, which is exactly the kind of bug that surfaces as "works locally, breaks in a clean CI checkout."
Migrating an Existing Monorepo
Switching package managers on a live monorepo isn't free, every package-lock.json/yarn.lock gets replaced, CI caching configuration needs updating to point at the new lockfile and cache directory, and any script that shells out to a specific package manager binary needs a pass. The realistic path is running the new package manager on a branch, comparing the resolved dependency tree against the old lockfile for any unexpected version changes, then treating the switch as its own reviewed change rather than bundling it into an unrelated feature branch where a resolution difference could get missed.
Conclusion
pnpm's disk efficiency and strong warm-cache performance make it the reasonable default for a new monorepo in 2026, especially on CI runners or developer machines running multiple projects. Yarn PnP wins on cold-install speed specifically and adds real dependency-hygiene enforcement, at the cost of occasional tooling friction. npm remains the lowest-friction choice when a project is small enough that none of these differences are actually felt yet.