The k6 executor isn’t just a mode; it’s the engine that dictates how your load test actually runs, and choosing the wrong one can make your results completely misleading.

Let’s see the per-vu-iterations executor in action:

import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  scenarios: {
    my_scenario: {
      executor: 'per-vu-iterations',
      vus: 10, // Number of VUs
      iterations: 5, // Number of iterations per VU
      maxDuration: '30s', // Max duration for the scenario
    },
  },
};

export default function () {
  http.get('https://httpbin.k6.io/get');
  sleep(1);
}

In this example, we’ve defined a single scenario named my_scenario. The executor: 'per-vu-iterations' tells k6 to run exactly 10 virtual users (vus: 10), and each of those 10 VUs will execute the default function exactly 5 times (iterations: 5). The total number of requests will be vus * iterations, so 10 * 5 = 50. The maxDuration is a safety net; if the 50 iterations take longer than 30 seconds, the scenario will stop.

Now, what problem does this solve? Imagine you need to simulate a specific number of user actions rather than a fixed rate of requests. For instance, you want to simulate 5 distinct actions by each of your 10 users, regardless of how fast or slow those actions are. The per-vu-iterations executor guarantees that each VU completes its allotted iterations. This is different from a time-based approach where the number of iterations depends heavily on how quickly the system responds.

The core of the per-vu-iterations executor is its deterministic nature regarding the total number of iterations. It’s designed for scenarios where you want to ensure a fixed amount of work is done by each concurrent user. This is particularly useful when testing specific workflows or user journeys where the number of steps is more critical than achieving a precise requests-per-second (RPS) target. The total number of iterations across all VUs will be vus * iterations. k6 will spawn vus virtual users, and each will run the script iterations times. The test will end when all VUs have completed their assigned iterations or when maxDuration is reached.

The surprising part is how this executor handles system slowdowns. If the target system becomes sluggish, the per-vu-iterations executor will extend the test duration. Each VU will simply take longer to complete its iterations, but it will eventually finish them (unless maxDuration is hit). This is in contrast to rate-limiting executors (like constant-arrival-rate) which might drop requests or fail to meet their RPS targets if the system can’t keep up. This behavior makes per-vu-iterations excellent for understanding how your system behaves under sustained, albeit potentially slow, load, rather than just aiming for a specific throughput.

The next logical step is to consider how to simulate a constant rate of requests arriving at your system, which is where the constant-arrival-rate executor shines.

Want structured learning?

Take the full K6 course →