Quick Answer

npm ships with Node and is the safe default for most projects. pnpm is significantly faster and uses far less disk by hard-linking a shared store, and it is stricter about undeclared dependencies. Yarn's modern versions add advanced features but need more setup. The choice matters less than consistency — pick one per project, commit its lockfile, and never mix two in the same repository.

What They All Do

All three read package.json, resolve the dependency tree, download packages from a registry, and write a lockfile recording the exact versions installed.

The lockfile is the important part. package.json usually specifies ranges — ^4.18.0 means "4.18.0 or any compatible newer version". The lockfile records what was actually resolved, so a teammate or a server installs the identical tree rather than whatever is newest today.

npm          → package-lock.json
yarn         → yarn.lock
pnpm         → pnpm-lock.yaml

Commit the lockfile. Not committing it is a common mistake that produces builds differing between machines and mysterious failures that appear only in CI.

The commands map closely, which is why switching is easy:

npm install          yarn            pnpm install
npm install pkg      yarn add pkg    pnpm add pkg
npm install -D pkg   yarn add -D     pnpm add -D pkg
npm uninstall pkg    yarn remove     pnpm remove pkg
npm run build        yarn build      pnpm build
npm ci               yarn --frozen-lockfile   pnpm install --frozen-lockfile

Where They Genuinely Differ

Disk usage and the node_modules layout. This is the biggest practical difference.

npm and Yarn Classic copy every dependency into each project's node_modules. Ten projects using React means ten copies on disk. A single node_modules folder commonly reaches hundreds of megabytes.

pnpm stores each package version once in a global content-addressable store and creates hard links into each project. Ten projects using the same React version share one copy on disk. On a machine with many projects the saving is measured in gigabytes.

Strictness. npm and Yarn Classic produce a flat node_modules, which means you can require a package you never declared, simply because a dependency of yours installed it. That works until the day that transitive dependency changes and your code breaks for no visible reason.

pnpm uses a nested layout where only your declared dependencies are reachable. Undeclared imports fail immediately, which is stricter and correct — though it does surface pre-existing problems when migrating an old project.

Speed. pnpm is generally fastest, particularly on repeat installs where the store is already populated. npm has improved substantially and is no longer notably slow. Yarn Classic sits between them.

Which to Choose

Choose npm when: you are learning, working on a small project, or want zero setup. It ships with Node, every tutorial assumes it, and every platform supports it without configuration. For a fresher building projects, this is the right default and the differences will not affect you.

Choose pnpm when: you have many projects on one machine and disk space matters; you want the fastest installs; you are working in a monorepo; or you want strictness about declared dependencies. It is the technically strongest option and adoption has grown steadily.

Choose Yarn when: the project already uses it, or your team has settled on its modern workspace features. Yarn's newer versions changed significantly from Yarn 1, including an optional mode that removes node_modules entirely — powerful, but with real compatibility caveats worth understanding before adopting.

What actually matters more than the choice:

  • Use one per project and commit its lockfile.
  • Use the frozen-lockfile variant in CInpm ci or the equivalent — so builds are reproducible and fail loudly when the lockfile disagrees with package.json.
  • Do not switch mid-project without deliberately removing the old lockfile and node_modules.

Never Mix Them in One Project

This causes real, confusing problems and it happens easily — one developer runs npm install, another runs yarn, and now the repository has two lockfiles.

package-lock.json     ← npm's view of the tree
yarn.lock             ← Yarn's view, resolved separately

They can resolve different versions of the same transitive dependency. So the application works for one developer and fails for another, and CI may install a third combination depending on which command it runs. Debugging this is genuinely painful, because the code is identical and only the installed tree differs.

If it has already happened: decide on one, delete the other lockfile and node_modules, reinstall cleanly, and commit the result.

rm -rf node_modules yarn.lock
npm install
git add package-lock.json && git rm --cached yarn.lock

Prevent it by declaring the intended manager in package.json, which modern Node can enforce:

{
  "packageManager": "pnpm@9.0.0"
}

Add the other lockfiles to .gitignore as a second line of defence, and mention the expected command in your README.

Practical Notes Worth Knowing

npm ci is not the same as npm install. It deletes node_modules and installs exactly what the lockfile specifies, failing if the lockfile and package.json disagree. It is faster and reproducible, which is why it is the correct command in CI and deployment. npm install may resolve new versions and modify the lockfile — fine locally, wrong in automation.

Dependencies versus devDependencies. Runtime code goes in dependencies; build tools, linters and test frameworks go in devDependencies. Production installs commonly skip devDependencies, which is why a misplaced package produces "cannot find module" in production while working locally.

Use npx for one-off tools rather than installing them globally. It downloads, runs and discards, which avoids version conflicts and permission problems:

npx create-react-app my-app

Avoid sudo npm install -g. It creates root-owned files that cause permission errors afterwards, which people then fix with more sudo. Use a Node version manager so everything lives in your home directory.

Audit occasionallynpm audit reports known vulnerabilities. Read before running --force, since it can install breaking major versions to resolve an advisory that may not affect how you use the package.

Frequently Asked Questions

Is pnpm better than npm? Technically it is faster, uses far less disk by sharing one copy of each package version, and is stricter about undeclared dependencies. npm is simpler and needs no setup, which makes it the better default while learning.
Can I use npm and Yarn in the same project? No. Two lockfiles can resolve different versions of the same transitive dependency, so the project works for one developer and fails for another. Pick one, delete the other lockfile and node_modules, and reinstall.
Should I commit the lockfile? Yes, always. It records the exact versions that were installed and tested, so teammates and servers get an identical tree. Not committing it is a common cause of builds that differ between machines.
What is the difference between npm install and npm ci? npm install resolves versions and may update the lockfile. npm ci deletes node_modules and installs exactly what the lockfile specifies, failing if it disagrees with package.json. Use ci in CI and deployments.
Why does my package work locally but not in production? Most often it is listed in devDependencies while being needed at runtime, and production installs skip those. Case-sensitive import paths on Linux servers are the other common cause.