# How to Iterate Over Variable Sets in Probe Steps: A Complete Guide

> Learn to iterate over variable sets in Probe steps. Use the iteration field to run actions repeatedly with dynamic data accessed via {{vars.key}} templates. Master Probe for efficient automation.

- Repository: [Tomohisa Oda/probe](https://github.com/linyows/probe)
- Tags: how-to-guide
- Published: 2026-03-06

---

**Use the `iteration` field in Probe steps to run the same action multiple times with different variable sets, referencing each iteration's data via `{{vars.key}}` templates.**

Probe, the lightweight workflow automation tool by `linyows/probe`, provides a powerful mechanism to execute steps repeatedly across different datasets without duplicating YAML definitions. By leveraging the `iteration` field, you can parameterize steps with variable sets that override default values and execute sequentially during job runtime.

## Understanding the Iteration Mechanism in Probe

The iteration system relies on two core components: the `Step` struct definition that stores variable sets, and the job execution logic that processes these sets sequentially.

### The Step Struct and Iteration Field

In [`step.go`](https://github.com/linyows/probe/blob/main/step.go) at line 26, the `Step` struct defines the `Iteration` field as a slice of maps, where each map represents a distinct variable set:

```go
type Step struct {
    // ... other fields ...
    Iteration []map[string]any `yaml:"iteration"`   // holds the variable sets
    // ... other fields ...
}

```

Each map in the `Iteration` slice contains key-value pairs that override the step's default `vars` during that specific execution cycle.

### Job Execution Logic

The job controller in [`job.go`](https://github.com/linyows/probe/blob/main/job.go) (lines 80-84) determines whether to enter iteration mode based on the presence of variable sets:

```go
if len(st.Iteration) == 0 {
    j.executeStep(st, &idx, ctx, nil)          // normal execution
} else {
    j.executeStepWithIterations(st, &idx, ctx) // enter iteration mode
}

```

When iterations exist, the system calls `executeStepWithIterations` instead of the standard step executor.

## Implementing Variable Set Iteration in Your Probe Configuration

### Basic YAML Syntax for Iteration

Define the `iteration` field as a list of maps under any step. Each map represents one execution cycle with its specific variable values. The following example from [`examples/iteration-literal.yml`](https://github.com/linyows/probe/blob/main/examples/iteration-literal.yml) demonstrates the complete syntax:

```yaml
jobs:
- name: Run iteration example
  steps:
  - name: "4 times iterations: {{vars.name}}"
    uses: shell
    with:
      cmd: echo "ID={{vars.id}} Name={{vars.name}} Role={{vars.role}}"
    test: |
      res.code == 0 && indexOf(res.stdout, vars.name) >= 0
    echo: "{{vars}}"
    vars:                     # default values (used if a key is missing in an iteration)

      id: 1234
      name: default
      role: admin
    iteration:
    - {id: 2000, name: foo, role: user}
    - {id: -3000, name: bar, role: editor}
    - {id: 0.123, name: baz, role: guest}
    - {id: "5000", name: qux, role: guest}

```

This configuration executes the shell step four times, substituting `{{vars.id}}`, `{{vars.name}}`, and `{{vars.role}}` with the values from each iteration map.

### Using Default Variables with Iteration Sets

The `vars` field at the step level defines default values that apply when an iteration map omits specific keys. In [`examples/iteration-literal.yml`](https://github.com/linyows/probe/blob/main/examples/iteration-literal.yml), the default `id: 1234` would only apply if an iteration entry lacked the `id` key. This merging behavior ensures backward compatibility while allowing selective overrides:

```yaml
vars:
  id: 0          # fallback if an iteration does not provide `id`

  role: viewer   # fallback role

iteration:
  - {name: Alice, id: 101}  # Uses id: 101, role: viewer

  - {name: Bob}             # Uses id: 0, role: viewer

```

### Conditional Execution with skipif and Iteration Variables

Iteration variables are accessible in conditional expressions such as `skipif`. This allows you to skip specific iterations based on their variable values:

```yaml
steps:
- name: Conditional step
  uses: shell
  with: {cmd: "echo hi"}
  skipif: vars.name == "skip-me"
  iteration:
    - {name: "run-me"}
    - {name: "skip-me"}

```

In this example, the second iteration is omitted because the `skipif` condition evaluates to `true` when `vars.name` equals `"skip-me"`.

## How Probe Processes Iteration Internally

### The executeStepWithIterations Function

Located in [`job.go`](https://github.com/linyows/probe/blob/main/job.go) at lines 96-100, the `executeStepWithIterations` function handles the actual looping logic:

```go
func (j *Job) executeStepWithIterations(st *Step, idx *int, ctx *JobContext) {
    for _, vars := range st.Iteration {
        j.executeStep(st, idx, ctx, vars)      // each vars becomes the step's "vars"
    }
}

```

This function iterates over the `st.Iteration` slice, passing each variable map to `executeStep` as the `vars` parameter. The step index (`idx`) is incremented once per iteration, ensuring each execution appears as a distinct entry in the final report.

### Context Merging in SetCtx

Before each iteration executes, the `Step.SetCtx` method (in [`step.go`](https://github.com/linyows/probe/blob/main/step.go), lines 20-34) prepares the execution context by merging job-level variables with the current iteration's override map:

```go
func (st *Step) SetCtx(j JobContext, override map[string]any) {
    // ... setup evalCtx with job vars and outputs ...
    evalCtx := StepContext{
        Vars:        j.Vars,
        Outputs:     outputs,
        RepeatIndex: j.RepeatCurrent,
    }
    // ... evaluate step-level vars using the iteration map (override) ...
    st.ctx = StepContext{Vars: merged, Outputs: outputs, ...}
}

```

The `override` parameter contains the current iteration's variable map. This map is merged with the step's default `vars`, allowing iteration-specific values to take precedence while preserving defaults for unspecified keys.

### Template Evaluation

During execution, templates within `with`, `test`, `echo`, and other step fields are evaluated against the prepared context. The `vars` namespace provides access to the merged variables:

- `{{vars.id}}` – Accesses the iteration's `id` value
- `{{vars.name}}` – Accesses the name variable
- `{{vars.role}}` – Accesses the role variable

Each iteration evaluates templates independently, ensuring that conditional logic and output formatting reflect the current variable set.

## Summary

- **Use the `iteration` field** in Probe steps to execute the same action multiple times with different variable sets, defined as a list of maps in your YAML configuration.
- **Reference iteration variables** in templates using the `{{vars.key}}` syntax within `with`, `test`, `echo`, and `skipif` expressions.
- **Leverage default values** by defining a `vars` map at the step level; iteration maps override specific keys while unspecified keys fall back to defaults.
- **Understand the execution flow**: `Job.executeSteps` detects iterations and calls `executeStepWithIterations`, which loops through variable sets and invokes `Step.SetCtx` to merge contexts before each run.
- **Track individual executions**: Each iteration increments the step index independently, producing distinct entries in the final execution report.

## Frequently Asked Questions

### What is the iteration field in Probe steps?

The `iteration` field is a YAML array of maps defined within a step that enables **data-driven testing** by running the same step multiple times with different inputs. Each map in the array represents one execution cycle, with its key-value pairs overriding the step's default variables for that specific run. According to the `linyows/probe` source code in [`step.go`](https://github.com/linyows/probe/blob/main/step.go), this field is parsed into `[]map[string]any` and processed by the job executor when present.

### How do I reference iteration variables in templates?

Iteration variables are accessed using the **`{{vars.key}}`** template syntax within step fields such as `with`, `test`, `echo`, and `skipif`. For example, if an iteration map contains `{name: "Alice", id: 101}`, you can reference these values as `{{vars.name}}` and `{{vars.id}}`. The `Step.SetCtx` method in [`step.go`](https://github.com/linyows/probe/blob/main/step.go) merges these iteration-specific values into the step context before template evaluation, ensuring each iteration uses its own variable set.

### Can I use default values with iteration sets?

Yes, you can define **default variables** using the `vars` field at the step level. When an iteration map omits a specific key, the step falls back to the value defined in `vars`. For example, if `vars` defines `role: viewer` and an iteration entry only specifies `{name: "Bob"}`, the step will use `role: viewer` for that execution. This merging behavior is handled in [`step.go`](https://github.com/linyows/probe/blob/main/step.go) during context preparation, allowing you to specify only the values that differ across iterations while maintaining consistent defaults.

### How does Probe handle step indexing with iterations?

Each iteration is treated as a **distinct step execution** for reporting purposes. The `executeStepWithIterations` function in [`job.go`](https://github.com/linyows/probe/blob/main/job.go) increments the step index (`idx`) once per iteration when calling `executeStep`. This means if a step has four iteration entries, the final report will contain four separate entries (e.g., steps 1, 2, 3, 4) rather than a single aggregated result. This granular indexing allows you to identify exactly which variable set produced specific outputs or failures in the execution report.