skip to content

In k6, how do exec.scenario.iterationInTest and exec.vu.iterationInInstance differ?

level: middleimportance: should knowfreq 51%

answer

  1. one is mine, one is everyone's
  2. counters start at zero
  3. the legacy global mirrors the VU tally
  4. unique per scenario, not per test
  5. scenario reuse resets only one of them

basics

~20 s

exec.scenario.iterationInTest numbers the iterations of one scenario across every VU and every k6 instance, so no two iterations share a value. exec.vu.iterationInInstance counts only what a single VU has run, so many VUs report 0 at the same moment.

solid answer

~40 s

k6 exposes four iteration counters and they differ on two axes: whose iterations are counted, and how far the uniqueness reaches. `exec.vu.iterationInInstance` is a **private tally** for one VU, so ten VUs finishing their first iteration all report `0`; the legacy global `__ITER` mirrors it exactly. `exec.vu.iterationInScenario` is the same idea but restarts when the VU moves to another scenario. `exec.scenario.iterationInInstance` counts iterations of the scenario across all VUs in one k6 process, and `exec.scenario.iterationInTest` does so across the whole run, which makes it the only one safe as a unique key. All four are **zero-based**, unlike VU ids. The catch: `iterationInTest` is unique per scenario, not per test.

code

javascript · 16 lines
javascript
import exec from 'k6/execution';

export const options = {
  scenarios: {
    orders: { executor: 'shared-iterations', vus: 3, iterations: 9 },
  },
};

export default function () {
  console.log(
    `vu=${exec.vu.idInInstance}` +
      ` vuIter=${exec.vu.iterationInInstance}` +
      ` vuIterInScenario=${exec.vu.iterationInScenario}` +
      ` scenarioIterInTest=${exec.scenario.iterationInTest}`
  );
}

go deeper

for a junior

Start with the two names: anything on exec.vu counts what your own virtual user did, anything on exec.scenario counts what the whole scenario did. All of them begin at 0.

for a middle

Be able to say which counter is safe as a unique key and why. Only exec.scenario.iterationInTest is allocated once per iteration of the scenario across every VU and instance.

for a senior

The failure to describe is the silent one: a dataset keyed on a per-VU counter runs green while touching only the first few rows and reusing each of them across many VUs, so no test fails and the coverage requirement is quietly unmet.

for a principal

Decide up front how data uniqueness is guaranteed across a suite. Once scripts grow a second scenario the per-scenario counters overlap, so the convention for slicing data has to be chosen before the split, not after.

## Four counters, not one k6 exposes four iteration counters through the `k6/execution` module, and they differ along two axes: **whose iterations are being counted** (this VU, or every VU in the scenario) and **how wide the uniqueness guarantee reaches** (one k6 process, or the whole run). All four are **zero-based**, unlike VU ids, which start at 1. | Expression | Counts | Unique across | | --- | --- | --- | | `exec.vu.iterationInInstance` | iterations this VU has run, ever | this VU alone | | `exec.vu.iterationInScenario` | iterations this VU has run in the current scenario | this VU within that scenario | | `exec.scenario.iterationInInstance` | iterations of this scenario in this k6 process | all VUs of the scenario, one process | | `exec.scenario.iterationInTest` | iterations of this scenario in the whole run | all VUs, all processes, that scenario | The legacy global `__ITER` is not a fifth counter. k6 sets it to exactly the same value as `exec.vu.iterationInInstance` immediately before each iteration begins. ## The two that are usually confused `exec.vu.iterationInInstance` is a **private tally**. Each VU keeps its own, so when ten VUs finish their first iteration, all ten report `0`. It is useful for "do this only on my first pass" logic and useless as a unique key, because the values collide across VUs by design. `exec.scenario.iterationInTest` is a **shared ticket dispenser**. k6 hands out one number per iteration of that scenario, across every VU and every k6 instance, so no two iterations of the scenario ever see the same value. That is what makes it the right expression for "give this iteration a row of test data that nobody else gets". ## The caveat that catches people `exec.scenario.iterationInTest` is unique **per scenario**, not per test. Each scenario in a `scenarios` map gets its own counter starting at 0, so a test with two scenarios produces two overlapping sequences. If you are using the counter to hand out unique dataset rows and you later split the script into two scenarios, the two scenarios will quietly start handing out the same rows. The fixes are to give each scenario its own slice of the data, or to key the lookup on the scenario name as well as the counter. ## What happens when a VU is reused k6 can hand an already-initialised VU to a second scenario. When that happens: - `exec.vu.iterationInInstance` **keeps counting**. A VU that ran 10 iterations in the first scenario reports `10` on its first iteration of the second -- the value is the VU's lifetime tally and is never reset while the VU exists. - `exec.vu.iterationInScenario` **restarts at 0** for the new scenario. - `exec.scenario.*` counters belong to the scenario, so the new scenario's counters begin at 0 regardless of which VU shows up. Because module-scope variables also survive that hand-over, "this VU has already logged in" logic written against `iterationInInstance` behaves consistently with the state it guards. ## Picking the right one 1. **Need a value no other iteration in this scenario will ever see?** Use `exec.scenario.iterationInTest`. It is the only counter with that guarantee, and it holds even when the run is split across instances. 2. **Need "only on my first iteration"?** Use `exec.vu.iterationInScenario === 0` if the guard should re-arm when the VU moves to a new scenario, or `exec.vu.iterationInInstance === 0` if it should fire exactly once in the VU's life. 3. **Need to correlate a log line with a specific iteration?** Print the VU id together with a VU counter; either one alone is ambiguous, and the pair is not. ## A worked mistake A script uses `__ITER` to index a 1000-row dataset and runs with 50 VUs and 1000 total iterations. Every VU runs 20 iterations, so `__ITER` only ever ranges over 0..19. Rows 20 to 999 are never touched, and each of the first 20 rows is used by 50 different VUs at once. The script runs green, the throughput numbers look fine, and the data-uniqueness requirement the test was written for is silently unmet. Swapping `__ITER` for `exec.scenario.iterationInTest` fixes it outright, because that counter is allocated once per iteration of the scenario rather than once per iteration per VU. ## Version note All four properties are current in k6 v2.x and live on the default export of `k6/execution` (`import exec from 'k6/execution'`). `__VU` and `__ITER` still exist but are documented as the discouraged form kept for scripts written before the module existed.

  • Two scenarios run in one k6 test. Are their `exec.scenario.iterationInTest` values distinct?
    No. The counter is allocated per scenario, so each scenario numbers its own iterations from 0 and the sequences overlap. If you use it to hand out unique dataset rows, give each scenario its own slice of the data or key the lookup on the scenario name as well.
  • How does `exec.vu.iterationInInstance` behave when a k6 VU is reused by a second scenario?
    It keeps counting. The value is the VU's lifetime tally and is never reset while the VU exists, so a VU that ran 10 iterations in the first scenario reports 10 on its first iteration of the second. `exec.vu.iterationInScenario` is the counter that restarts at 0.
  • Which counter gives a k6 script an 'only on my very first iteration' guard?
    `exec.vu.iterationInInstance === 0` fires exactly once in the VU's life, even if the VU is later handed to another scenario. Use `exec.vu.iterationInScenario === 0` instead when the guard should re-arm for each scenario the VU joins.

saying these in an interview costs you the question

  • Thinks exec.vu.iterationInInstance is unique across all k6 VUs
  • Uses __ITER to index a dataset expecting a unique row per iteration
  • Assumes exec.scenario.iterationInTest is unique across every scenario
  • Believes iterationInScenario and iterationInInstance are the same counter
  • Expects the counters to be one-based like k6 VU ids