npm Basics - JavaScript

  1. Which assessment of the resulting project is correct?
    Answer: npm does not create index.js, and it records Vitest under devDependencies because of the --save-dev flag. When working with npm, it helps to clearly separate what the CLI tool manages (the manifest and dependency records) from what it creates (actual source files). npm is a package manager, not a code generator — it won't produce your project's source files for you. Running npm init -y generates package.json with sensible defaults, including "main": "index.js". That field is simply a pointer telling Node.js where your entry point lives — it doesn't cause npm to create the file. If index.js doesn't exist, the field just points to nothing until you create it yourself. This makes C correct: npm does not create index.js, and Vitest is recorded under devDependencies because the --save-dev flag explicitly instructs npm to place the package there rather than under dependencies. A is wrong on two counts — npm never auto-generates source files, and --save-dev places packages in devDependencies, not dependencies. The claim that npm can "execute JavaScript files" has nothing to do with where a package is saved. B gets the devDependencies part right, but it still incorrectly claims npm creates index.js. This is a tempting half-right answer designed to catch students who know about --save-dev but misunderstand what npm init actually produces. D is entirely fabricated. npm writes to package.json immediately upon installation — it doesn't defer saving until a test run. A useful rule of thumb: "main" in package.json is a declaration, not a command. npm declares intent; you still have to write the code.
  2. Which statement best describes the resulting project state?
    Answer: eslint remains declared in devDependencies but is omitted from the deployment's installed packages. When working with npm, it's essential to distinguish between where a package is declared and whether it gets installed in a given environment. These are two separate concerns, and this question tests whether you understand that distinction. Running npm install --save-dev eslint does two things: it installs eslint locally and records it under devDependencies in package.json. That declaration is permanent until you explicitly remove it. Later, when the deployment pipeline runs npm install --omit=dev, npm skips installing any packages listed under devDependencies — but it does not modify package.json or package-lock.json. The declaration stays; only the installation behavior changes. This makes A the correct answer: eslint remains in devDependencies as declared, but the deployment environment simply doesn't install it. B is wrong because --omit=dev has no effect on how packages are categorized in package.json. No package ever "moves" between dependencies and devDependencies based on an install flag. C is wrong because deployment commands are read-only with respect to your manifest files — npm install never deletes entries from package.json or package-lock.json. D misrepresents what --omit=dev does entirely; it omits all dev dependencies from installation, not just newly added ones, and it doesn't selectively install eslint. A good mental model to carry forward: package.json describes what your project needs; install flags describe how the current environment should fulfill that description. Flags filter installation, not declarations.
  3. Which description best distinguishes the roles of package.json and package-lock.json?
    Answer: package.json expresses an allowed range, while the lockfile records the resolved 3.2.4 installation. When working with npm dependency management, it helps to think of package.json and package-lock.json as serving two distinct purposes: intention versus precision. package.json expresses what versions your project is willing to accept. A range like ^3.2.0 means "give me anything compatible with 3.2.0" — which includes 3.2.4 but excludes 4.0.0 (a breaking-change major bump). The lockfile, by contrast, records exactly what npm actually resolved and installed — in this case, 3.2.4 — so every developer and every CI environment installs the identical artifact. This is exactly what D describes, making it the correct answer. A is wrong on both counts: ^3.2.0 does not permit 4.0.0 (that would require ^4.0.0 or a broader range), and the lockfile doesn't exclude major version 3 — it pins within that range. B reverses the relationship entirely. package.json holds the version range, not a single pinned version, and package-lock.json records the resolved dependency tree for your project — not every version available in the registry. C confuses both files with something they aren't. Neither file records installed files on disk (that's node_modules) nor source-level imports in your code (that's your actual JavaScript files). A useful mental shorthand: think of package.json as a preference ("I'll take anything in this range") and package-lock.json as a receipt ("here's exactly what was delivered"). On exam questions about npm, always ask yourself whether the question is about acceptable ranges or resolved installations.
  4. What does npm primarily use to reconstruct the project's dependencies?
    Answer: It uses the dependency declarations and the lockfile's resolved versions to populate node_modules. When you see a question about npm install behavior, think about npm's two-file system: package.json declares what you want, and package-lock.json records exactly what was installed — specific versions, resolved URLs, and the full dependency tree. Understanding which file takes precedence in which scenario is the core concept being tested here. When a developer runs npm install with no arguments and a package-lock.json already exists, npm treats the lockfile as the authoritative source of truth. It reads the locked, resolved versions for every dependency and installs precisely those into node_modules. This is what makes lockfiles valuable for teams — everyone gets identical dependency trees regardless of when they run install. Answer A correctly captures this: npm uses both the dependency declarations in package.json and the lockfile's resolved versions together to reconstruct node_modules. Answer B describes something npm doesn't do at all — npm never performs static analysis on your source code to figure out which packages to install. Answer C is also fabricated behavior; npm resolves packages from the registry (or cache) scoped to your project, not from some global package list that it copies locally. Answer D is backwards — npm would never discard committed metadata like package.json or package-lock.json and reinitialize them from scratch. A useful tip: remember the difference between npm install (respects the lockfile if present) and npm update (can modify the lockfile by fetching newer versions within semver ranges). Exam questions often test whether you understand that the lockfile locks you into reproducible builds — that's its entire purpose.
  5. Which set of changes should the developer expect?
    Answer: date-fns is installed locally, added to dependencies, and recorded in a new lockfile. When you see a question about npm install <package>, think carefully about three things: where the package gets installed (local vs. global), which section of package.json it lands in, and what happens to the lockfile. Running npm install date-fns without any flags performs a local installation by default — the package goes into the project's node_modules folder. Since npm version 5+, this command also automatically saves the package to the dependencies field in package.json (no --save flag required anymore). Critically, npm simultaneously generates or updates package-lock.json to record the exact resolved versions of every installed package, ensuring reproducible installs across environments. This makes C the correct answer. A is wrong on two counts: npm install <package> installs locally by default, not globally. To install globally, you'd need the -g flag. A also incorrectly claims no manifest is created or changed. B gets the local installation right, but places date-fns in devDependencies — that only happens when you pass --save-dev. Regular dependencies (runtime code like date-fns) belong in dependencies, not devDependencies. B also ignores the lockfile entirely. D describes a caching behavior that simply doesn't match how npm install <package> works — the package is installed immediately into node_modules, not held in a pending cache state. A handy rule of thumb: npm install <package> → local, dependencies, lockfile updated. Add -g for global, --save-dev for devDependencies. Memorizing these flag behaviors will cover a wide range of npm-related exam questions.
  6. Which command most directly satisfies these requirements?
    Answer: npm ci, because it uses the lockfile strictly and performs a clean installation. When you see a CI/CD question involving package.json and package-lock.json, focus on two requirements: reproducibility (exact versions, every time) and strictness (fail rather than silently fix mismatches). npm ci — short for "clean install" — is purpose-built for this scenario. It deletes node_modules entirely before installing, reads only from package-lock.json, and exits with an error if package.json and the lockfile are out of sync. This makes B the correct answer: it performs a clean installation and enforces lockfile integrity, which is exactly what a CI pipeline needs. A is wrong because it describes behavior that npm ci has, not npm install. Running npm install will actually update the lockfile if it finds a discrepancy — the opposite of failing on mismatch. This makes it unsuitable for reproducible CI builds. C is wrong because npm init -y initializes or regenerates a package.json file with default values. It has nothing to do with reading a lockfile or installing dependencies — it's a project scaffolding command, not an install command. D is wrong because npm update actively resolves newer versions of packages within the semver ranges in package.json, potentially changing what gets installed. It doesn't respect the lockfile as a fixed source of truth. A useful memory aid: think of npm ci as the "no surprises" command. If anything is unexpected, it refuses to proceed — exactly the behavior you want when deploying to production or running automated tests.
  7. What is the expected result of these commands?
    Answer: A default package.json is created in /work, while inventory-app remains without one. When working with npm and the terminal, the key concept to understand is that CLI commands always operate on the current working directory — not on directories you've recently created or referenced. Here's what happens step by step: mkdir inventory-app creates a new folder inside /work, but it does not navigate into it. Your shell's current directory is still /work. When you immediately run npm init -y, npm looks at where you currently are — /work — and initializes a package.json there. That makes A the correct answer: a default package.json lands in /work, and inventory-app stays empty. B is wrong because npm init has no awareness of which directory was "most recently created." It doesn't track mkdir history — it simply reads your current working directory from the shell environment. C is wrong for the same reason; npm doesn't cascade into subdirectories or duplicate initialization. It touches exactly one directory: wherever you currently are. D is wrong because npm init -y has no requirement that the current directory be empty. It will happily create a package.json alongside existing files, or even overwrite one if asked. A reliable mental model: think of your terminal like a cursor in a file system. Until you explicitly run cd, that cursor doesn't move. mkdir creates a folder but leaves you exactly where you were. On exam questions involving npm init, always trace where the terminal is pointing before deciding where the file gets created.
  8. Which command best uses the project's existing ESLint executable?
    Answer: npx eslint ., which resolves the executable from the project's local package installation. When working with Node.js projects, understanding how executables get resolved is essential. The key distinction here is between local and global package installations — and how to run a locally installed tool without adding anything new to the project. When you run npm install, binaries from your dependencies land in node_modules/.bin/. The challenge is that your shell doesn't know to look there by default. That's exactly what npx solves: it searches node_modules/.bin/ first, finds the locally installed eslint executable, and runs it. So npx eslint . — option B — is the correct choice. It uses what's already there without any new installations or declarations. Option A fails the core requirement. Running npm install --global eslint would install ESLint globally, which both modifies the machine's global state and contradicts the constraint of not adding another package declaration or installation step. Option C is a trap — npm init is used to scaffold a new project or run initializer packages, not to execute binaries from devDependencies. It won't invoke ESLint at all in this context. Option D describes a command that sounds plausible but doesn't work as advertised. npm run executes scripts defined in your package.json's "scripts" field — it doesn't directly invoke arbitrary package binaries by name, and passing a dot as an argument this way won't behave as expected. A useful mental shortcut: whenever a question asks you to run a locally installed CLI tool without modifying anything, think npx. It's purpose-built for resolving and executing binaries from your local node_modules.
  9. Which command most directly performs the requested cleanup?
    Answer: npm uninstall express, removing the local package and updating dependency metadata. When working with npm, it helps to think about the three places a dependency "lives": the package.json manifest (declaration), package-lock.json (locked resolution), and node_modules (installed files). A true cleanup means removing the package from all three — that's exactly what npm uninstall is designed to do. Running npm uninstall express removes the local files from node_modules, deletes the entry from dependencies in package.json, and updates package-lock.json to reflect the removal. Option D is the single command that satisfies every requirement in the scenario. The distractors each represent a real command or flag, but none of them do what's described. A invents a nonexistent syntax — npm cache clean accepts no package-name argument to selectively remove dependency declarations; it only clears npm's internal download cache and doesn't touch package.json or node_modules. B uses a real flag (--omit=prod) that tells npm to skip installing production dependencies during an install run — it doesn't remove anything and would actually leave Express declared in package.json. C fabricates a --remove flag; npm init regenerates or creates a package.json interactively but has no option to surgically remove a specific package. A good study habit here is to remember that npm uninstall is the mirror image of npm install — just as npm install <pkg> adds a package everywhere it needs to exist, npm uninstall <pkg> removes it from everywhere. When a question asks about "removing a dependency cleanly," that symmetry should point you straight to D.
  10. Which command correctly installs and classifies the package?
    Answer: npm install @acme/formatter --save-dev, using the complete scoped package name. When working with npm, two concepts are being tested here simultaneously: scoped package syntax and dependency classification flags. Getting either one wrong means the command fails or miscategorizes the package. Scoped packages in npm always follow the format @scope/package-name. The @ symbol is mandatory and signals to npm that this is a namespaced package, and the forward slash / separates the scope from the package name. To install it only as a development dependency — meaning it won't be bundled in production — you add the --save-dev flag, which writes the package under devDependencies in your package.json. That makes C (npm install @acme/formatter --save-dev) the correct command: proper scoped syntax plus the right classification flag. Looking at the distractors: A drops the @ prefix entirely, writing acme/formatter. Without @, npm interprets this as a GitHub repository shorthand (user/repo), not a scoped package — so it would try to install from GitHub, not the npm registry. B uses a colon (@acme:formatter) instead of a forward slash. This is simply invalid npm syntax; npm won't recognize the package identifier and will throw an error. D gets the scoped name right (@acme/formatter) but uses --global instead of --save-dev. Global installation makes the package available system-wide rather than placing it in the project's development dependencies — the opposite of what the scenario requires. As a study tip, remember the scoped package formula: **@ + scope + / + name**. Any variation in that punctuation is a red flag in a multiple-choice answer.

Start this quiz