Lesson 6 of 30
A cold install is the single biggest recurring cost in most pipelines. Caching turns a two-minute download into a ten-second restore — if the cache key is right.
14 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Every time a pipeline runs, it starts from nothing: a fresh virtual machine, no node_modules, no downloaded packages. Left alone, that means every single run — every push, every PR, every re-run after a flaky failure — re-downloads the entire dependency tree from the registry. For a mid-sized project that's often 60-120 seconds of a job spent doing something that produced the exact same result five minutes ago.
Caching fixes this by saving a directory (usually the package manager's store, not node_modules itself) after a run, keyed to something that only changes when the dependencies actually change — the lockfile. Next run, if the lockfile hash matches a saved cache, the pipeline restores that directory instead of hitting the network. If the lockfile changed, the key changes, the old cache misses, and you pay the download cost exactly once more — not on every run after.
This project's .github/workflows/ci.yml doesn't use the generic actions/cache action directly — it uses the cache option built into actions/setup-node, which does the same job with less YAML:
- uses: pnpm/action-setup@v4
with:
version: 11.5.0
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
cache: pnpm tells setup-node to hash pnpm-lock.yaml, use that hash as the cache key, and restore pnpm's global store into the runner before pnpm install runs. pnpm install --frozen-lockfile then finds most (often all) packages already sitting in the local store and links them instead of downloading — the difference between a ~90 second cold install and a ~10 second warm one.
The equivalent written out by hand with actions/cache — useful to know because not every setup action has a cache: shortcut — looks like this:
- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ~/.local/share/pnpm/store
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
The key is exact-match: it only hits when the hash is identical, meaning the lockfile hasn't changed at all. restore-keys is a fallback prefix match — if no exact key hits, GitHub Actions restores the most recent cache whose key starts with that prefix, so a lockfile change still gets you a mostly warm cache (everything unchanged is still there) instead of a fully cold one.
First run: no cache exists yet. pnpm install downloads everything fresh — 45s. Its cache key is a hash of pnpm-lock.yaml.
This is the same trade-off Docker's build cache makes at the image layer level — reuse the expensive, unchanged part of a build and redo only what actually changed, keyed off a hash of the inputs rather than off wall-clock time or a person's judgment call. Where a Docker layer is invalidated by a changed COPY or RUN instruction, a CI dependency cache is invalidated by a changed lockfile hash — different mechanism, identical shape of problem.