The primary reason pnpm often blows npm out of the water on performance and disk space isn’t a clever algorithm, but a fundamentally different approach to how it stores packages.
Let’s see this in action. Imagine you have a project that depends on lodash.
# Install lodash with npm
npm install lodash
Now, let’s say you create a second project and install lodash there too.
# Install lodash in a second project with npm
cd ../another-project
npm install lodash
With npm, each project gets its own copy of the lodash package, even though it’s identical. This is a lot of wasted disk space and duplicated effort.
Now, let’s do the same with pnpm.
# Install lodash with pnpm (assuming pnpm is installed)
pnpm add lodash
And then in a second project:
# Install lodash in a second project with pnpm
cd ../another-project
pnpm add lodash
Here’s where it gets interesting. pnpm doesn’t copy lodash into another-project’s node_modules. Instead, it uses a global store. When you install a package, pnpm downloads it once to a central, content-addressable store on your machine (typically ~/.pnpm-store).
Then, for each project, pnpm creates a node_modules directory that is a symlink forest. It creates hard links from the project’s node_modules to the global store. This means that if lodash is 10MB, and you install it in 10 projects, you only ever store those 10MB once.
This global store is the core of pnpm’s efficiency. It’s not just about saving space; it’s about how node_modules are structured. Instead of a deep, nested structure where dependencies of dependencies are also nested, pnpm flattens the structure and uses symlinks to point to the correct packages in the global store.
To understand the mental model, think of node_modules as a highly organized library. npm is like each patron getting their own personal shelf for every book they borrow, even if it’s the same edition. pnpm, on the other hand, has a central archive (the global store). When you need a book, pnpm gives you a special pointer (a symlink) that directs you to the exact copy in the archive. This pointer lives in your project’s node_modules directory.
This symlink approach means that node_modules in a pnpm project is much smaller and faster to generate. When you run pnpm install, pnpm checks its global store. If a package version is already there, it just creates the symlinks. If not, it downloads it to the store and then creates the symlinks.
Crucially, pnpm’s node_modules structure is non-flat. This might sound like a drawback, but it’s actually a feature. npm and yarn (v1) used to create deeply nested node_modules structures. Later, they adopted a "hoisting" strategy to flatten node_modules as much as possible, putting common dependencies at the top level to reduce duplication. However, this hoisting can lead to "phantom dependencies" – where your project can accidentally access packages it didn’t explicitly declare as dependencies, leading to brittle builds and difficult-to-debug issues. pnpm’s symlink forest ensures that a package can only access its direct dependencies and the dependencies of those dependencies, enforcing a stricter and more reliable dependency graph.
The speed comes from less I/O. Instead of copying megabytes of files for each project, pnpm primarily creates lightweight symlinks. The initial download to the global store happens once, and subsequent installations just link to existing files. This is significantly faster, especially for large projects or when working with many projects on the same machine.
One of the most surprising aspects is how pnpm handles different package versions. If you have Project A needing lodash@4.17.10 and Project B needing lodash@4.17.21, pnpm doesn’t just store them side-by-side in the global store. It stores each unique version once, and then projects link to the specific version they require. This is a form of deduplication that npm and yarn historically haven’t achieved as effectively at the disk level.
The next hurdle you’ll likely encounter is understanding how pnpm manages workspaces and its unique pnpm-workspace.yaml configuration.