npm vs npx vs pnpm vs yarn (They’re Not All Doing the Same Thing)
January 22, 2026
I’ve typed npm install probably ten thousand times. Took me way too long to realize what npm actually does compared to npx, and even longer to understand why people keep talking about pnpm and yarn.
Turns out they’re solving different problems.
The One Everyone Knows: npm
npm is the package manager that comes with Node.js. You probably use it every day.
npm install lodash\nnpm run dev\nnpm update
It downloads packages from the npm registry, puts them in node_modules, and manages your package.json and package-lock.json files.
Simple enough. But here’s what’s actually happening when you run npm install:
-
Reads your
package.json -
Figures out all the dependencies (and their dependencies, and their dependencies...)
-
Downloads everything from the registry
-
Unpacks it all into
node_modules -
Creates/updates
package-lock.jsonwith exact versions
The package-lock.json file locks down exact versions of everything. That’s why your coworker gets the same versions you do, even if newer versions exist. It’s deterministic - same lock file means same installation.
The Confusing One: npx
First time someone told me to run npx create-react-app my-app, I thought it was a typo.
npx isn’t a package manager. It’s a package runner.
npx create-react-app my-app\nnpx prettier --write .\nnpx eslint --init
It downloads the package temporarily, runs it, then discards it. You don’t install it globally, you don’t add it to your project - you just run it once.
This is great for:
-
CLI tools you run once (like
create-react-app) -
Tools you want to try without installing
-
Running specific versions:
npx webpack@4.46.0 --version -
Avoiding global installations cluttering your system
The thing that confused me: npx will also run locally installed packages from node_modules/.bin. So if you have prettier installed in your project, npx prettier runs that version, not a temporary download.
It’s basically “run this command, figure out where to get it from, I don’t care about the details.”
The Fast One: pnpm
pnpm showed up claiming to be faster and more disk-efficient than npm. I ignored it for a while because “another package manager?” felt unnecessary.
Then I tried it on a project with a massive node_modules folder. The difference was noticeable.
pnpm install lodash\npnpm run dev
Commands look the same. But what’s happening underneath is different.
npm/yarn: Every project gets its own copy of every package. Ten projects using lodash? Ten copies of lodash on your disk.
pnpm: Uses a content-addressable store. One copy of lodash on your disk, hard-linked into each project that needs it.
My node_modules folders got way smaller. Installations got faster because most packages were already on disk from other projects.
There’s also a strictness thing: pnpm only lets you import packages you explicitly installed. With npm/yarn, you can accidentally import packages that are sub-dependencies of your dependencies. Works until it doesn’t, then you’re confused why your build breaks.
pnpm forces you to be explicit. Some people love this. Some people find it annoying.
Lock file is pnpm-lock.yaml. Different format from npm’s lock file, but same idea - locks down exact versions.
The Other One: yarn
Yarn came out in 2016 when npm was slower and less reliable. Facebook, Google, and others built it as an alternative.
yarn add lodash\nyarn dev\n```\n\nDifferent commands, same outcome. Yarn was faster than npm at the time, had better caching, and the lock file (`yarn.lock`) was more reliable.\n\nThen npm got better. npm 5+ added `package-lock.json` and improved speed. Suddenly yarn's advantages were less obvious.\n\nBut yarn kept evolving. Yarn 2+ (now called "Berry") is a complete rewrite with some... controversial changes:\n- Plug'n'Play mode (no `node_modules` folder at all)\n- Packages stored in `.zip` files\n- Different resolution algorithm\n\nSome teams love it. Many found it too different and stuck with yarn 1.x or moved back to npm.\n\nClassic yarn (1.x) is stable, fast, widely used. Yarn 2+ is innovative but polarizing.\n\n## When I Actually Use Each\n\n**npm:** \n- It's already there (comes with Node)\n- Most projects use it\n- Good enough for most things\n- I'm not optimizing for speed or disk space\n\n**npx:**\n- Running one-off commands (`create-react-app`, `prettier`)\n- Trying tools without installing them\n- Running different versions of things for testing\n\n**pnpm:**\n- Large monorepos where disk space matters\n- Projects with tons of dependencies\n- When I want stricter dependency rules\n- When installation speed actually matters (CI/CD)\n\n**yarn:**\n- Working on projects that already use it\n- Teams that prefer its workflow\n- Honestly, less often now that npm is good\n\n## The Performance Thing\n\nRan a quick test installing a medium-sized project's dependencies on my laptop:\n```\nnpm install: ~45 seconds\nyarn install: ~30 seconds \npnpm install: ~25 seconds (first time)\npnpm install: ~8 seconds (cached)
Your mileage will vary based on project size, network speed, and whether packages are cached.
For small projects? Doesn’t matter. For big projects or CI/CD pipelines running hundreds of installs a day? Those seconds add up.
The Lock File Situation
Every package manager has a different lock file format:
npm: package-lock.json (JSON format, verbose) yarn: yarn.lock (custom format, human-readable) pnpm: pnpm-lock.yaml (YAML format)
They’re not compatible. If your project has a yarn.lock, use yarn. If it has package-lock.json, use npm. Mixing them causes problems.
Most teams pick one and stick with it. Switching mid-project is a pain.
The Node Modules Problem
Traditional npm/yarn: node_modules folders are massive, nested, duplicate-heavy. Ever seen a node_modules folder with 200MB for a simple project? That’s why.
pnpm’s approach with hard links helps. Yarn’s Plug’n’Play eliminates node_modules entirely but requires everything to support it (not everything does).
There’s no perfect solution. Just trade-offs:
-
npm/yarn classic: Works everywhere, takes lots of space
-
pnpm: Saves space, slightly different resolution
-
yarn PnP: Saves even more space, breaks some tools
The Honest Take
For most projects, npm is fine. It’s slower than alternatives but not by enough to matter for day-to-day work. It’s there, it works, everyone knows it.
Use pnpm if:
-
You have huge projects
-
Disk space is limited
-
You want faster CI builds
-
You like stricter dependency resolution
Use yarn if:
-
Your team already uses it
-
You prefer its commands
-
You’re using workspaces heavily (yarn’s workspace support is nice)
Use npx for:
-
One-off command execution
-
Avoiding global installs
Don’t overthink it. Pick one, configure your editor/scripts for it, move on. The package manager isn’t what makes or breaks your project.
The Thing That Actually Matters
Whichever you pick, commit the lock file to git. Seriously. That’s more important than which tool you use.
Without the lock file, different developers get different versions. Builds break randomly. “Works on my machine” becomes a real problem.
With the lock file, everyone gets identical dependencies. Builds are reproducible. Life is better.
I’ve seen teams argue for hours about npm vs yarn vs pnpm. I’ve also seen teams argue whether they should commit the lock file or not. Then they forget to commit the lock file and spend days debugging version mismatches.
Get the basics right first.
What I Wish Someone Told Me Earlier
They’re all fine. npm works. yarn works. pnpm works.
The differences matter more as projects scale. For a small personal project, use whatever’s already installed. For a large team with monorepos and CI/CD, maybe invest time evaluating pnpm or yarn workspaces.
But honestly? Most of the time, just use npm and focus on actually building things.
The package manager isn’t the bottleneck. You are.
Thanks for reading Under The Hood! Subscribe for free to receive new posts and support my work.