Command Palette

Search for a command to run...

Hectal
PHASE 1Beginner ~14 min· topic 3 of 4

Topic 1.3

Caching & Speeding Up Builds

In one line

Re-downloading every dependency and rebuilding everything from scratch on every single run is genuinely wasteful — caching lets a pipeline reuse work from a previous run whenever nothing relevant has actually changed.

0/4 · 0%

Think of it like this

Repacking your entire suitcase from an empty dresser every single morning, instead of just adding today's one new item to what's already packed — caching is keeping the parts that genuinely haven't changed (your dependencies, most of the time) rather than redoing that work from absolute zero on every single pipeline run.

Key ideas

  1. 01

    Without caching, EVERY pipeline run starts from a genuinely fresh, empty workspace on a hosted runner — every single dependency (Maven's ~/.m2 repository, Gradle's own cache, node_modules for a JavaScript project) gets downloaded again from scratch, every single time, even when the actual dependency list hasn't changed at all between runs.

  2. 02

    A CACHE KEY determines whether a previously-saved cache can be reused — commonly derived from a hash of the dependency-lock file itself (pom.xml, package-lock.json) specifically, since dependencies only genuinely need re-downloading when that file actually changes; an unchanged lock file means the exact same cache can be safely reused run after run.

  3. 03

    actions/cache (GitHub Actions' own caching action) saves a specified directory (Maven's ~/.m2, or a node_modules folder) keyed by that hash, and RESTORES it automatically on a later run if the key matches — a cache MISS (the key changed, meaning dependencies genuinely changed) correctly falls back to a full, fresh download, exactly as it should.

  4. 04

    This directly connects to Phase 0.3's build systems — Gradle's own INCREMENTAL BUILD system (skipping tasks whose inputs haven't changed, visible as UP-TO-DATE in its own console output) is conceptually the exact same idea as CI-level dependency caching, just operating at the level of individual build TASKS rather than the whole dependency download step.

  5. 05

    Caching genuinely speeds up pipelines significantly (often turning a several-minute dependency download into a few-second cache restore), but a STALE or INCORRECTLY-KEYED cache can cause genuinely confusing failures — a cache that was keyed too broadly (not actually tied to the real dependency file) might restore OUTDATED dependencies silently, which is exactly why the cache key must genuinely reflect what would actually invalidate the cache's correctness.

In your stack

  • →

    For a Node project, caching node_modules (or, more commonly and more safely, npm's own global cache directory) keyed on package-lock.json's hash provides the identical speedup — a pipeline that would otherwise run npm install from scratch on every single run instead restores from cache almost instantly whenever the lock file hasn't changed.

Code & diagrams

CacheHitVsMissdiagram

The lock file's hash decides everything — unchanged means a near-instant restore; changed means a correct, fresh download.

Rendering diagram…
caching.ymlmarkdown

Maven dependency caching, keyed correctly on the actual dependency declaration file.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with: { java-version: "21", distribution: "temurin" }

      - name: Cache Maven dependencies
        uses: actions/cache@v4
        with:
          path: ~/.m2/repository
          key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
          restore-keys: |
            ${{ runner.os }}-maven-

      - run: mvn clean package

Explain it without notes

01

Why is a dependency-lock file's hash a genuinely good choice for a cache key, rather than something simpler like the current date?

02

What real risk does an incorrectly-configured, too-broad cache key introduce?

Practice

01

Add dependency caching to a real pipeline (adjusted for your build tool) and compare the pipeline's total run time before and after — confirm a subsequent run with an unchanged lock file shows a clear cache hit and a meaningfully faster total time.

02

Deliberately change your dependency file (add or update one dependency) and confirm the next pipeline run shows a cache MISS, correctly re-downloading fresh dependencies.

Trade-offs

  • ↔

    Caching trades a small amount of storage (the cache itself needs to be saved and stored between runs, usually with a real size limit and eventual expiry on hosted platforms) for a genuinely significant, often dramatic reduction in pipeline run time — for any project with a non-trivial number of dependencies, this trade is close to universally worth making, which is exactly why dependency caching is one of the first optimizations most real teams add to a working pipeline.

Done when you can

  • I understand what a cache key is and why it should be derived from a dependency-lock file's hash.

  • I can add dependency caching to a real pipeline and measure the resulting speedup.

  • I understand the real risk of an incorrectly-configured, too-broad cache key.