Free — no signup required

The Core Metric: Time to Feedback

3 min read

Inner vs. Outer Loop

To optimize CI, you must first understand the two loops every developer operates in simultaneously:

  1. Inner Loop (Local): Everything that happens on your laptop before you push. You write code, hit save, and your IDE shows a red squiggly line. Your local test runner catches a failing assertion. Feedback time: milliseconds to seconds.
  2. Outer Loop (CI/CD): Everything that happens after you push. The server queues your job, spins up a runner, installs dependencies, builds artifacts, runs integration tests, and reports back. Feedback time: minutes to hours.

The inner loop is fast because it is local, incremental, and purpose-built for speed. The outer loop is slow because it is comprehensive, isolated, and shared infrastructure. The goal of platform engineering is to push as much signal as possible into the inner loop and make the outer loop as fast as the inner loop's constraints allow.

Optimizing the Outer Loop

Driving "Time to Feedback" down in the outer loop requires three concrete strategies:

1. Fail Fast — Order by Cost
Run the fastest, cheapest checks first. A linting check that takes 30 seconds should always run before an end-to-end test suite that takes 20 minutes. If a developer forgot a semicolon, they should know in 30 seconds, not after waiting for the entire pipeline to complete.

The mental model: sort your pipeline stages by (cost_to_run / probability_of_catching_error). Cheap checks that catch common errors go first.

2. Parallelization — Run Independent Jobs Concurrently
Sequential pipelines are almost always wrong. If linting, unit tests, and security scanning are all independent of each other, there is no reason to run them one after another. Defining them as three separate jobs (each with its own checkout and run step) rather than three sequential steps within one job means they run simultaneously on separate runners.

With this structure, all three jobs start simultaneously. Total pipeline time is max(lint_time, unit_test_time, security_scan_time) — not the sum.

3. Change Detection — Skip What Did Not Change
If a developer only changed README.md, there is no reason to rebuild the Docker image, re-run the Java test suite, or re-deploy the staging environment. Change detection lets you skip pipeline stages that are irrelevant to the files that actually changed.

In GitHub Actions, this looks like a step that diffs the current commit against its parent (checked out with enough history to make that comparison possible), checks whether any changed path matches the source directory or manifest file, and writes a boolean output that a later step's if: condition gates on — skipping the build entirely when nothing relevant changed.

In monorepos, tools like Nx, Turborepo, and Bazel handle change detection at a much more sophisticated level — tracking not just file changes but the entire dependency graph, so that changing a shared library correctly triggers rebuilds of all downstream packages.

Key Point: The three levers for outer loop optimization are ordering (fail fast), concurrency (parallelize), and scope (change detection). Applied together, they can reduce a 45-minute pipeline to under 10 minutes without removing a single test.

This is one of 18 chapters

Every chapter is free — Kubernetes, Terraform, SRE, distributed systems, and more — with fast daily review built in.

Sign up free