Skip to main content
Virtual Users (VUs) are the core concept in k6 load testing. A VU executes your test script repeatedly, simulating a real user interacting with your system.

What is a Virtual User?

A VU is a virtualized user that runs your test script. Each VU executes independently and concurrently, running the default function in a loop.
With 10 VUs and a 1-second sleep, this test generates approximately 10 requests per second.

VU Lifecycle

Based on the k6 source code in lib/execution.go, VUs have a sophisticated lifecycle:

Initialization

From lib/execution.go, k6 tracks VU initialization:
  • initializedVUs - Total number of initialized VUs
  • activeVUs - VUs currently executing the test script
  • VU IDs start from 1 (for backwards compatibility)
  • Each VU gets unique local and global identifiers

VU States

A VU transitions through these states:
1

Initialized

VU is created and ready to use, stored in the VU buffer.
2

Active

VU is retrieved from the buffer and executing the default function.
3

Returned

VU completes an iteration and returns to the buffer for reuse.

Planned vs Unplanned VUs

k6 distinguishes between two types of VUs based on the source code in lib/execution.go:

Planned VUs

Planned VUs are pre-initialized before the test starts. They’re allocated based on your executor configuration.

Unplanned VUs

Unplanned VUs are initialized dynamically when arrival-rate executors need more VUs than pre-allocated.
When k6 initializes an unplanned VU, you’ll see: “Initializing an unplanned VU, this may affect test results”

VU Execution Model

From lib/executor/shared_iterations.go, k6 implements a sophisticated VU execution model:

VU Retrieval

The GetPlannedVU() function retrieves VUs from a shared buffer channel:
  • VUs wait in a buffer channel when not in use
  • Executors borrow VUs when needed
  • VUs are returned to the buffer after use
  • If retrieval takes longer than 400ms, k6 logs a warning

VU Activation

When a VU starts an iteration:
  1. It’s retrieved from the buffer
  2. The activeVUs counter increments
  3. It executes the default function
  4. The activeVUs counter decrements
  5. It returns to the buffer

Configuring VUs

Constant VUs

Maintain a fixed number of VUs for the entire test:

Ramping VUs

Gradually increase or decrease VUs using stages from examples/stages.js:

Per-Scenario VUs

Different scenarios can use different VU configurations:

VU Metrics

k6 automatically emits VU-related metrics defined in metrics/builtin.go:

vus

Current number of active VUs (Gauge metric):

vus_max

Maximum number of VUs initialized during the test (Gauge metric).

VU Iterations

From lib/execution.go, k6 tracks two types of iterations:

Full Iterations

Iterations that complete normally are counted in fullIterationsCount:

Interrupted Iterations

Iterations cut short by:
  • Test duration ending
  • VU ramping down
  • Manual test interruption (Ctrl+C)
  • Threshold failures with abortOnFail
These are tracked in interruptedIterationsCount and reflected in the dropped_iterations metric.

VU Resources and Memory

Each VU maintains its own:
  • JavaScript runtime (Sobek/goja)
  • Execution context
  • Variable scope
  • Iteration state
VUs are resource-intensive. A single machine typically handles 100-1000 VUs depending on test complexity and hardware.

VU Execution Patterns

Shared Iterations

From lib/executor/shared_iterations.go, VUs share a total iteration count:
Each VU attempts iterations until the total is reached.

Per-VU Iterations

Each VU executes a specific number of iterations:

Advanced VU Concepts

VU Identifiers

From lib/execution.go, each VU receives unique identifiers:

VU Tags

All metrics emitted by a VU are automatically tagged:

Graceful Stop

When ramping down, VUs complete their current iteration before stopping:
Set gracefulStop longer than your longest iteration to avoid interruptions.

Best Practices

1

Start Small

Begin with a small number of VUs and gradually increase to find limits.
2

Use Stages for Realistic Load

Ramp up VUs gradually to simulate realistic traffic patterns.
3

Monitor VU Metrics

Watch vus and vus_max to ensure your test runs as expected.
4

Consider Arrival Rates

For consistent throughput, use arrival-rate executors instead of VU-based executors.

Common Patterns

Load Test Pattern

Stress Test Pattern

Virtual Users are the foundation of k6 load testing. Understanding how they work enables you to design effective, realistic load tests.