Some decisions in software development feel trivial — right up until they blow up in your face. Choosing a package manager is one of those. I've paid every tax: projects with 4GB node_modules folders, deploys failing over version conflicts that "shouldn't exist", monorepos taking 8 minutes to install in CI. All of that made me obsessive about this topic.
So let's cut straight to it: pnpm vs npm vs yarn vs bun — what they are, what makes them different, when to use each one, and which one won my heart (and my .zshrc).
A little history so you understand why this mess exists
npm landed in 2010, bundled with Node.js. It was the only option and, honestly, it was a disaster. The flat node_modules we know today didn't even exist — old versions gave you infinite nested trees, folders inside folders with paths so deep that Windows just straight-up gave up. I'm not exaggerating.
Yarn showed up in 2016, built by Facebook (now Meta) alongside Google and others. It was a breath of fresh air: parallel installs, deterministic lockfile, local cache. npm took years to catch up.
pnpm came around the same era but took longer to gain traction. And Bun... Bun is the newcomer that showed up in 2023 claiming everyone else is slow — and it's not entirely wrong.
npm: the one you already have installed
The good: It ships with Node. No extra install, no explaining anything to anyone. For a small project or onboarding someone new, nothing beats it for simplicity.
The bad: It's still the slowest of the four on cold installs. The node_modules is a flat monster that cheerfully duplicates packages everywhere. On a medium-sized monorepo I watched node_modules hit 3.8GB. That's not normal. That's a problem.
Version 7 brought workspaces, version 8 improved performance considerably, and npm today is decent. But "decent" isn't enough when better alternatives existed years ago.
My real experience: In 2021 I kicked off a project with npm by default. Three months in, CI was spending 6 minutes on npm install alone. I migrated to pnpm on a Friday afternoon — my mistake, never migrate anything important on a Friday — and by Monday CI was down to 90 seconds. That left a mark on me.
When to use it: When the project is small, when the team doesn't want setup friction, or when you're working with tools that have known bugs with pnpm (they exist, though fewer every year).
Yarn: the one that promised a lot and got complicated
Classic Yarn (v1) was genuinely revolutionary at the time. Deterministic lockfile, a cache that actually worked, real parallelism. npm took literal years to match those features.
Then Yarn Berry (v2 onwards) arrived and everything got complicated. They introduced Plug'n'Play (PnP): instead of node_modules, a custom resolution system where packages live in .zip files and a loader resolves them at runtime. The theory is beautiful. The practice is a different story.
I tried Yarn Berry on a Next.js project and spent an entire afternoon debugging why certain packages wouldn't load. Some tools in the ecosystem simply don't understand the PnP model. I ended up setting nodeLinker: node-modules in .yarnrc.yml, which is basically Yarn Berry pretending to be classic Yarn. So... what's the point?
The good of Yarn: The DX when it works is genuinely nice. Yarn workspaces are very mature. The Yarn team has been polishing this for years.
The bad: The fragmentation between v1 and v2/v3/v4 is a mess. Search for an error on Stack Overflow and 60% of the answers are for the wrong version. And PnP, while conceptually brilliant, creates real friction.
When to use it: Classic Yarn (v1) on legacy projects that already use it and aren't worth migrating. Yarn Berry if you're willing to invest the time to actually understand PnP and your whole team is on board.
pnpm: my favorite, no contest
This is where I get intense, so bear with me.
pnpm solves the fundamental problem of Node package management in an elegant way: the content-addressable store. Instead of copying packages into every node_modules, it uses hard links to a global store on your machine. lodash installed across 47 different projects takes up disk space exactly once. The node_modules in each project is mostly symlinks and hard links.
This has real consequences:
- Speed: First install is comparable to npm/yarn. Second install and beyond: ridiculously fast.
- Disk space: I have maybe 200 projects on this machine. If I used npm that'd be hundreds of GB. With pnpm the global store sits at ~15GB for everything.
-
Correctness: pnpm is strict about dependencies. You can't access a package you didn't declare in your
package.json. This feels like a nuisance until you realize that npm/yarn silently let you access transitive dependencies — a ticking time bomb.
pnpm workspaces are the best in the ecosystem, full stop. The pnpm-workspace.yaml file is simple, hoisting is configurable, and the pnpm -r command to run scripts across all packages is a gem.
My real experience: Today I use pnpm on every single personal and professional project I touch. The command I type most often after git is probably pnpm install. I've had exactly one compatibility issue in two years: a legacy library that assumed npm-style hoisting. Solved in 10 minutes with a tweaked .npmrc.
The bad of pnpm: The initial install is one extra step. Some open-source projects with legacy npm configs can be a headache. And the global store can grow large if you don't run pnpm store prune occasionally — I have it on a cron job.
When to use it: Almost always. Especially on monorepos. Especially if you care about disk space. Especially if you want your dependencies to be correctly declared.
Bun: the one that came to break everything
Bun isn't just a package manager — it's a complete runtime (a Node replacement), a bundler, a test runner, and a transpiler. But here we're talking about it as a package manager.
The numbers: Bun install is absurdly fast. I'm talking cold installs of mid-sized projects in 2-3 seconds. What npm does in 45 seconds, Bun does in 3. It's written in Zig, uses its own runtime, and has a binary cache that makes repeated installs nearly instantaneous.
I tried it on a Next.js project and it worked perfectly. The lockfile (bun.lockb) is binary, which is conceptually weird but works. Workspaces are supported. Compatibility with the npm ecosystem is very good.
But here's my hesitation: Bun as a runtime still has edge cases where behavior diverges from Node. If you use Bun only as a package manager (with Node as the runtime), that goes away — but then you're installing a massive tool just to use a fraction of it.
The Bun ecosystem matured a lot in 2024. By 2025 it's a real, serious option. But I haven't migrated my production projects because the risk/benefit doesn't close for me. pnpm's speed with cache is more than enough, and Bun's "all-in-one" mental model still gives me uncertainty in complex deployments.
When to use it: If you're starting a new project and want to experiment. If install performance is critical for you (massive monorepo on CI with no cache?). If you're the kind of person who likes living on the cutting edge and can handle the occasional rough edge.
The table everyone wants
| npm | Yarn | pnpm | Bun | |
|---|---|---|---|---|
| Speed (cold) | Slow | Medium | Fast | Very fast |
| Speed (cache) | Medium | Fast | Very fast | Very fast |
| Disk space | Heavy | Heavy | Light | Light |
| Monorepos | Basic | Good | Excellent | Good |
| Maturity | High | High | High | Medium |
| Compatibility | Total | High | High | High |
| Strictness | No | No | Yes | No |
My final recommendation, no sugarcoating
New project in 2025? → pnpm. Zero hesitation. The learning curve is minimal (the interface is almost identical to npm), the benefits are immediate and real, and ecosystem support is excellent.
Monorepo? → pnpm with workspaces. Best available option today.
Want to live in the future? → Bun. Just know you'll be beta testing some things.
Legacy project that already has a yarn.lock or package-lock.json? → Don't migrate for the sake of migrating. The lockfile is a contract. If you don't have a real performance or correctness reason, leave it alone.
Plain npm in 2025 with no specific reason: No. Not anymore. It's like using jQuery on a new project — it works, but there are better options and you know it.
The JavaScript ecosystem has its thousand problems, but on package management we've reached a point where we have genuinely good options. Take advantage of that. I stayed on npm way too long out of pure inertia, and those 6 minutes of CI back in 2021 are a debt I'm still settling somehow.
Top comments (0)