Travis CI Matrix Builds: Pruning the Cartesian Product for Faster Feedback
Use Travis CI matrix exclude and include to prune impossible runtime combos and add edge-case jobs, cutting build minutes while keeping cross-platform coverage.
26 Jul 2025, 14:51 UTC

A test suite passes on Ubuntu with Node 20 and fails on macOS with Node 18 because of a native dependency. Running every OS and runtime combination sequentially adds up to an hour of feedback. A Travis CI matrix runs the same script across combinations in parallel, but an unchecked Cartesian product burns build minutes fast.
How the matrix expands
The matrix key in .travis.yml accepts lists for job-level keys such as os, language runtime and env. Travis computes the Cartesian product of those lists. Two OSes times three Node versions creates six job definitions that share the same script unless overridden.
matrix:
os:
- ubuntu-22.04
- osx-14
node:
- "18"
- "20"
- "22"
The matrix definition creates six combinations. Each combination is rendered as a separate job in the build matrix.
Removing invalid pairs with exclude
Not every combination is valid. A legacy Node version may be unsupported on a newer macOS release, leading to predictable failures during native-module compilation. exclude prunes known-bad pairs before they are queued.
matrix:
os:
- ubuntu-22.04
- osx-14
node:
- "18"
- "20"
- "22"
exclude:
- os: osx-14
node: "18"
With the exclude entry the definition yields five combinations instead of six. The build page should not list the osx-14 / node 18 row.
Adding edge cases with include
include appends discrete job definitions that can override any key, including script. This is useful for a nightly runtime or an extra lint step that does not follow the general pattern.
matrix:
os:
- ubuntu-22.04
- osx-14
node:
- "20"
- "22"
include:
- os: ubuntu-24.04
node: "nightly"
script:
- npm ci
- npm run lint
- npm test
The include adds a custom job for ubuntu-24.04 + nightly with its own script while the other jobs use the default script section.
Worked example: Python versions with version-specific linting
A Django library targets Python 3.9–3.12 on Ubuntu 20.04 and 22.04. Python 3.12 is not available on 20.04, and ruff linting is desired only on the newest runtime.
language: python
matrix:
os:
- ubuntu-20.04
- ubuntu-22.04
python:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
exclude:
- os: ubuntu-20.04
python: "3.12"
include:
- os: ubuntu-22.04
python: "3.12"
script:
- pip install ruff
- ruff check .
- pytest
script:
- pip install -r requirements.txt
- pytest
The definition is 3×2 = 6 combinations minus one excluded plus one included job with a custom script. The build log for the ubuntu-22.04 / python 3.12 job can be inspected to confirm the ruff step is present.
Trade-offs: minutes and variable shadowing
Each matrix job consumes build minutes. A 10×5 matrix at 5 minutes per job uses 250 minutes per push, which can exhaust a typical free-tier quota. Prune aggressively and consider allow_failures for experimental runtimes instead of adding them to the main matrix.
Variables defined in include blocks shadow global env values. A global API_URL overridden in one include will cause that single job to hit a different endpoint. Document overrides inline with comments to avoid environment-specific debugging.
Verify before you merge
- Push the
.travis.ymlto a feature branch. - Open the Travis build page and count the matrix rows.
- Open a job and check the Environment section to confirm injected variables match the exclude/include logic.
If the row count matches the intended combinations and environment variables are correct, the matrix provides coverage with minimal waste.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.