The short answer
For high-frequency dependency updates, neither strategy alone is the right choice. The most reliable balance is a hybrid key list: put the strict checksum key first, followed by one or two progressively broader fallback keys. This gives you exact-match integrity when the lockfile is unchanged and graceful degradation to the newest compatible cache when it isn't.
- restore_cache:
keys:
- v1-deps-{{ checksum "package-lock.json" }}
- v1-deps-
# ... install step reconciles the lockfile ...
- save_cache:
key: v1-deps-{{ checksum "package-lock.json" }}
paths:
- node_modules
This works because of two well-established CircleCI behaviors (verify against current docs for your plan): restore_cache evaluates keys in order and restores the first match, including prefix matches, and caches are immutable once written — a fallback restore can never overwrite or pollute the strict key. After the install step runs, save_cache writes a fresh cache under the new checksum key, so the fallback acts purely as a bootstrap, not a correctness mechanism.
Where the fallback risk actually lives
The danger of fallback keys is not the cache itself — it's a stale restored tree. A cache built from an older lockfile may contain removed or downgraded packages. The mitigation is non-negotiable: your install step must reconcile the lockfile rather than trust the restored directory. Use npm ci, yarn install --frozen-lockfile, or the equivalent for your package manager so extraneous or missing packages are corrected deterministically. If your install step just checks "does node_modules exist?", fallbacks will eventually bite you.
When a full checksum miss outweighs fallback risk
Prefer strict-only keys when:
- Installs are fast (small tree, good registry latency) — the fallback saves little and adds staleness surface area.
- Stale artifacts cause hard-to-debug failures, e.g., native modules compiled against a different Node version, or postinstall scripts that don't re-run on reconcile.
- You use floating version ranges without a lockfile — checksum keys become unstable and both strategies degrade.
Fallbacks clearly win when installation is dominated by large downloads or compilation (big monorepo trees, native dependencies) and your package manager deterministically reconciles the lockfile. A partial restore typically cuts most of the download time even on a checksum miss.
Branch isolation
Adding {{ .Branch }} to keys prevents cross-branch pollution but destroys cross-branch cache hits. Only do this when branch-specific native builds genuinely conflict; otherwise the epoch prefix (v1-) is enough for manual busting.
Verify before committing
- Run a test pipeline with both strategies, bump one dependency, and compare restore hit type, install duration, and total job time over several runs.
- After a fallback restore, run an integrity check (
npm ls or lockfile verification) to confirm no extraneous packages survive. - Confirm in job output that new checksum keys are written on save.
Caveat: partial-match semantics and cache immutability are plan- and version-sensitive CircleCI behaviors — confirm them in the current documentation, and measure your actual restore-versus-install times rather than assuming fallbacks always win.