Last verified April 2026 · 7 min read
When to parallelise and when to serialise
COUNTER-INTUITIVE CLAIM
Parallelisation costs you money. Sometimes it also slows you down. The assumption that “more parallel = faster = better” is one of the most expensive unchecked beliefs in CI configuration.
When parallelism wins
- ✓Independent test suites that share no state and have no ordering dependency. Each shard runs in isolation.
- ✓Matrix strategies across 3+ OS or language versions where you genuinely need all combinations to pass before merge.
- ✓Long-running single-job pipelines (30+ minutes) that can be decomposed into independent sub-jobs.
- ✓Teams where developer time cost outweighs CI cost: a 20-developer team can justify spending $2/day extra to save 10 minutes per PR.
When serial wins
- !Short total wall-time (under 5 minutes). Parallel job overhead (runner startup, cache restore, checkout) can exceed the parallelism benefit.
- !Sequential dependency chains (build must complete before test, test before deploy). You cannot parallelise these regardless of cost.
- !Cost-sensitive teams where latency does not matter: a nightly build that takes 60 min serial vs 20 min parallel costs 3x as much in the parallel version for zero benefit to developer velocity.
Concurrency gates: the most underused optimisation
The GitHub Actions concurrency group cancels in-progress runs when a new run starts for the same group key. This single YAML change saves 20-40% of billed minutes on active teams where developers force-push.
# Cancel superseded builds on the same branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: trueVariant for release branches where you never want to cancel (you always want the release build to complete):
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/heads/release') }}Worked example: a 20-developer team averaging 3 force-pushes per PR, 15 PRs active at any time. Without concurrency gate: up to 45 concurrent builds on the same branches. With concurrency gate: 15 builds (one per branch). That is 30 builds cancelled, saving 30 × average build time in billed minutes per hour of active development.
Matrix strategy cost model
A 3 OS × 3 Node versions × 2 DB variants matrix is 18 jobs. Model the real cost before enabling it.
MATRIX COST MODEL: 3 OS × 3 NODE × 2 DB = 18 JOBS
Is running all 18 matrix combinations on every PR worth $507/month? For most teams, the answer is no. Consider running the full matrix only on main/merge-queue, and a reduced matrix (1 OS × latest Node × 1 DB) on feature branches.
fail-fast strategy
strategy:
fail-fast: true # cancel remaining matrix jobs on first failure
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: ['18', '20', '22']Fail-fast is true by default in GitHub Actions. Explicitly disable it (fail-fast: false) only when you want to see all matrix failures simultaneously, which is useful for debugging compatibility issues but wasteful in normal operation.