npm Interview Questions
These questions come up in JavaScript, Node.js, and fullstack interviews. Deep knowledge of npm, dependencies, and the package ecosystem signals strong engineering judgment.
1. What is the difference between npm, yarn, and pnpm?
All three are JavaScript package managers that install packages from the npm registry. npm (Node Package Manager) is the default bundled with Node.js - widely compatible but historically slower. yarn (by Meta, 2016) was created to address npm's speed and reliability issues with deterministic installs (yarn.lock), parallel downloads, and offline caching. yarn v2/berry introduced Plug'n'Play (no node_modules, packages stored in a zip cache). pnpm is the newest and most efficient: it uses a content-addressable store where each version of a package is stored once globally, then hard-linked into node_modules. This means 100x faster installs if packages are cached, uses far less disk space, and by default creates a strict node_modules structure (only declared dependencies are accessible).
2. What is the purpose of package-lock.json and when should you commit it?
package-lock.json records the exact version, resolved URL, and integrity hash of every installed package (including transitive dependencies). While package.json specifies ranges (e.g., "^4.18.2"), the lock file pins the exact version that was installed (e.g., 4.18.3). This ensures every developer and CI environment installs identical dependencies, eliminating "works on my machine" issues caused by different package versions. Always commit package-lock.json for applications. For published libraries, many developers add it to .gitignore to avoid forcing consumers to use your exact dependency tree, though there are valid arguments for committing it to ensure test reproducibility. Never modify it manually - let npm manage it.
3. What is the difference between dependencies and devDependencies?
dependencies are packages required at runtime in production. When someone installs your library, they also get your dependencies installed. devDependencies are only needed during development and testing: compilers (TypeScript, Babel), test frameworks (Jest, Vitest), linters (ESLint), and build tools (webpack, Vite). They are NOT installed when users install your package (npm install --production), and not installed in npm ci --production. For applications (not libraries), the distinction still matters: build tools should be devDependencies to keep production Docker images lean. Common mistake: putting TypeScript, @types/*, and ESLint in dependencies instead of devDependencies.
4. What are peer dependencies and when do you use them?
peerDependencies declare packages that your package expects to be present in the consumer's project but does NOT install automatically. They are used when your package is a plugin or extension of another package. Example: a React component library declares react and react-dom as peerDependencies. Why: if the library installed its own React, the app would have two copies of React running, breaking hooks (which depend on a singleton). The consumer provides the single shared React instance. Before npm 7, peer deps generated warnings only; npm 7+ installs them automatically (but fails on version conflicts). peerDependenciesMeta lets you mark specific peer deps as optional. Pattern: anything your plugin "plugs into" should be a peer dependency.
5. How does npm handle duplicate packages / dependency deduplication?
npm hoists shared dependencies to the top-level node_modules when possible. If package A and B both need lodash@4.17.21, npm installs one copy at node_modules/lodash. But if A needs lodash@3.x and B needs lodash@4.x, both copies are installed - lodash@4 at the top level and lodash@3 nested in node_modules/A/node_modules/lodash. This is flat deduplication. npm dedupe optimizes further by finding compatible versions that satisfy all requirements and removing duplicates. You can also use npm overrides to force all nested packages to use a specific version (useful for security fixes). pnpm handles this differently: it uses symlinks to a central store, never duplicating files on disk.
6. What is npm audit and how do you handle security vulnerabilities?
npm audit checks your dependency tree against the npm Advisory Database (which aggregates CVE data) and reports vulnerabilities by severity: critical, high, moderate, low. Run npm audit to see issues; npm audit fix to auto-upgrade vulnerable packages within their semver range; npm audit fix --force to apply breaking updates if needed. For false positives or acceptable risks, npm audit uses .npmrc: audit-level=high (only fail on high+). In CI, fail the build on high/critical: npm audit --audit-level=high. The overrides field in package.json can pin a vulnerable transitive dependency to a safe version even when the parent hasn't released a fix yet. Keep Dependabot enabled on GitHub for automated PR-based fixes.
7. What is the difference between npm install and npm ci?
npm install reads package.json ranges, resolves compatible versions, installs packages, and updates package-lock.json if needed. It is forgiving and flexible - good for development where you want the latest compatible versions. npm ci (clean install) reads package-lock.json exclusively and installs exactly those pinned versions. It deletes node_modules first, never modifies the lock file, and fails immediately if package-lock.json does not exist or does not match package.json. npm ci is faster (no resolution step), deterministic, and ideal for CI/CD pipelines. Rule of thumb: use npm install locally, use npm ci in CI environments.
8. How do npm workspaces help with monorepos?
npm workspaces (added in npm 7) allow managing multiple packages in a single repository with a shared node_modules. Define workspaces in the root package.json: "workspaces": ["packages/*", "apps/*"]. npm install from the root hoists shared dependencies, installs cross-workspace dependencies as symlinks, and deduplicates. This means apps/web can import @myorg/utils (a local package) without publishing it. You run scripts with npm run build --workspace=apps/web or across all packages with npm run test --workspaces. Compared to tools like Lerna (largely superseded) or Turborepo (caching layer on top of workspaces), npm workspaces handle installation and basic scripting; Turborepo adds incremental builds and caching.
9. How does semantic versioning protect consumers?
Semver (major.minor.patch) is a social contract between library authors and consumers. A patch bump (1.4.2 → 1.4.3) means only bug fixes - safe to auto-update. A minor bump (1.4.2 → 1.5.0) means new features added backward-compatibly - safe to auto-update. A major bump (1.4.2 → 2.0.0) means breaking changes - requires manual review and migration. The caret range "^1.4.2" allows npm to auto-install minor and patch updates (staying on major 1.x), trusting the library author's semver promises. This breaks when authors introduce accidental breaking changes in minor/patch releases (unfortunately common). For production stability, some teams use save-exact: true in .npmrc to pin all versions exactly.
10. What are dist-tags and how do you use them in a release workflow?
Dist-tags are human-readable labels pointing to specific package versions in the npm registry. The default tag is latest - this is what npm install pkg gives you. When publishing a prerelease, use npm publish --tag beta or --tag next to avoid updating latest. Users can then opt into prereleases explicitly: npm install pkg@beta. This is critical for not accidentally forcing users onto unstable versions. You can also use tags for LTS releases: npm dist-tag add pkg@1.10.5 lts-v1. Consumers pin to npm install pkg@lts-v1 and get updates within that line. When you are ready to promote a prerelease to stable: npm dist-tag add pkg@2.0.0-rc.3 latest - instantly all npm install pkg installs it.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free