npm — Node Package Manager — has been the default JavaScript package manager since Node.js shipped in 2009. It's older, less clever, and slower than every alternative in this quest. It's also the one you can guarantee every contributor's machine already has, the one every CI image already supports, and the one every JavaScript tutorial assumes. That universal floor is npm's superpower, and it's why it remains the default.
npm v11 (shipped alongside Node.js 24) requires Node.js ^20.17.0 || >=22.9.0. The recent improvements that matter day-to-day: faster installs via better caching, --ignore-scripts applies to all lifecycle scripts (a security hardening), and npm init now prompts for ESM vs CommonJS instead of defaulting silently.
npm has real limitations. The flat node_modules structure allows phantom dependencies — code that imports a package not declared in package.json but happens to be present because something else needed it. Installs are slower than pnpm or Bun (~57s vs ~9s for a Next.js install). The lockfile (package-lock.json) is verbose and merge-conflicts often. And npm doesn't natively support monorepo workflows as well as pnpm or Yarn.
None of those weaknesses outweigh npm's biggest strength: it's already there. For a hobby project, an open-source library, or a team you're not 100% sure about, npm is the safe default. Reach for pnpm or Bun when you want the speedup or the monorepo support.