CircleCI’s matrix jobs let you spin up multiple, independent test runs based on different combinations of environment variables, effectively testing your code against a matrix of configurations without writing redundant configuration.

Let’s see this in action. Imagine you have a Python project and you want to test it against Python 3.9, 3.10, and 3.11, and also test it on both Ubuntu 20.04 and 22.04. Instead of writing three separate jobs, each with different docker images and slightly different commands, you can define a matrix.

version: 2.1

jobs:
  build_and_test:
    docker:
      - image: cimg/python:<<matrix.python>>
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: pip install -r requirements.txt
      - run:
          name: Run tests
          command: pytest

workflows:
  version: 2
  test_matrix:
    jobs:
      - build_and_test:
          matrix:
            parameters:
              python:
                type: version
                values:
                  - "3.9"
                  - "3.10"
                  - "3.11"
              os:
                type: string
                values:
                  - "ubuntu2004"
                  - "ubuntu2204"
            alias: <<parameters.os>>-py<<replace-with-hyphens matrix.python>>

In this example, we’ve defined a single build_and_test job. The matrix section specifies two parameters: python and os. CircleCI will automatically generate a job for every unique combination of these parameters. So, for Python versions "3.9", "3.10", "3.11" and OS values "ubuntu2004", "ubuntu2204", it will create 3 * 2 = 6 distinct jobs.

The docker image dynamically pulls the correct Python version using <<matrix.python>>. The alias is a handy way to give each generated job a unique, descriptive name, making it easier to track in the CircleCI UI. The replace-with-hyphens filter in the alias is necessary because the os parameter values contain dots, which aren’t valid in job aliases.

The core problem matrix jobs solve is reducing configuration duplication and improving test coverage visibility. Without matrices, you’d have to copy-paste job definitions, each with a different docker image and potentially slightly different commands for each environment. This quickly becomes unmanageable and error-prone. With a matrix, you define the test logic once and specify the variations.

Internally, CircleCI’s orchestrator analyzes the matrix configuration and plans out all the individual job executions. Each job in the matrix runs independently, with its own set of environment variables derived from the parameters. This means a failure in one matrix combination doesn’t stop others from running, and you get clear results for each specific configuration.

The type: version for the python parameter is a special CircleCI type that understands semantic versioning and can intelligently pick specific patch versions if needed, though here we’re just specifying major.minor. The type: string is for general-purpose string values. You can also use type: boolean for true/false flags.

The alias field is crucial for understanding what’s happening in your builds. Without it, each generated job would just be named build_and_test. With alias: <<parameters.os>>-py<<replace-with-hyphens matrix.python>>, you’ll see jobs like ubuntu2004-py3.9, ubuntu2204-py3.10, and so on in your build history, making it immediately obvious which environment a particular test run corresponds to.

The replace-with-hyphens filter in the alias is a good example of how CircleCI allows transformations on parameter values within the configuration. This is essential for generating valid identifiers for your jobs. If you had other special characters or spaces in your parameter values, you might need different filters or custom commands to sanitize them for use in aliases or other configuration fields.

You can also define a filter within your matrix to skip specific combinations. For instance, if a particular Python version doesn’t support a certain OS, you can add a filter to prevent that job from being created and run.

The next logical step after mastering matrix jobs is exploring how to use context to manage shared dependencies and secrets across your matrix jobs, ensuring consistency and security without repeating configurations.

Want structured learning?

Take the full Circleci course →