In k6, how do the constant-vus and ramping-vus executors differ, and what options does each take?
answer
- fixed headcount versus a schedule
- each executor owns its own key pair
- the ramp needs a starting value
- stage entries carry duration and target
basics
~20 sk6's constant-vus executor holds a fixed number of VUs for a set time and takes vus plus duration. ramping-vus moves the count through a schedule and takes startVUs, stages and gracefulRampDown. Neither accepts the other's keys.
solid answer
~40 sBoth are VU-bounded executors in k6 v2: you say how many virtual users should be looping the exported `default` function, and each VU starts its next iteration as soon as the previous one returns. `constant-vus` takes `vus` (default `1`) and a required `duration` that must be at least `1s`; it plans every VU at time offset 0 and never changes the count. `ramping-vus` takes `startVUs` (default `1`), a required `stages` array of `{ duration, target }` entries, and `gracefulRampDown` (default `30s`), which no other executor has. Both also accept the shared per-scenario keys such as `startTime` and `exec`. Because k6 parses a scenario entry with unknown fields disallowed, putting `stages` in a `constant-vus` entry — or `duration` in a `ramping-vus` one — fails configuration instead of being ignored.
code
javascript · 15 linesimport http from 'k6/http';
export const options = {
scenarios: {
steady: {
executor: 'constant-vus',
vus: 200,
duration: '5m',
},
},
};
export default function () {
http.get('https://quickpizza.grafana.com/');
}go deeper
Memorise the two key pairs: constant-vus is vus plus duration, ramping-vus is startVUs plus stages. Being able to write either scenario entry from memory is the whole ask at this level.
Explain the defaults and the validation: vus and startVUs both default to 1, duration is required with a one-second floor, and stages needs at least one entry with both duration and target present.
Show that you know a mistyped key inside a scenario entry stops k6 before the run rather than being ignored, and that the same typo at the root of options only produces a warning.
Talk about which shape a team should standardise on. A flat constant-vus scenario is two numbers anyone can review; a stages array encodes a whole profile and needs a convention so scenarios stay comparable across services.
## Two ways to bound a k6 scenario by virtual users k6 v2 ships six executors, and two of them size a scenario by **virtual users (VUs)** rather than by an iteration budget or an arrival rate: `constant-vus` and `ramping-vus`. A VU in k6 is a JavaScript runtime with its own state that calls the script's exported `default` function in a loop; one call is one **iteration**, and k6 starts that VU's next one as soon as the previous returns. Both work that way. What separates them is whether the number of looping VUs is a single constant or a schedule. Both are written as one entry in the `scenarios` map with `executor` set to the executor's string id, and both accept the shared per-scenario keys every k6 executor has — `startTime`, `gracefulStop`, `env`, `exec`, `tags`, `options`. Everything below is what is unique to each of the two. ## `constant-vus` takes `vus` and `duration` - **`vus`** — how many VUs loop concurrently. Defaults to **`1`**, and k6 rejects a value of 0 or less. - **`duration`** — how long they loop. **Required**, and checked against a **one-second floor**: a `duration` of `500ms` is rejected with *"the duration must be at least 1s"*, and leaving it out is rejected with *"the duration is unspecified"*. k6 plans the whole `vus` count at time offset 0, so every VU is initialized before the scenario starts, and the count never moves while it runs. There is no third key: `constant-vus` is genuinely two numbers. ## `ramping-vus` takes `startVUs`, `stages` and `gracefulRampDown` - **`startVUs`** — the VU count at the scenario's first moment. Defaults to **`1`**; `0` is legal, a negative value is not. - **`stages`** — **required**, an array of `{ duration, target }` objects. Each `target` is the VU count to reach at the **end** of that stage, and k6 interpolates linearly from whatever count is running when the stage begins. Stage durations run consecutively, so their sum is the scenario's scheduled length. - **`gracefulRampDown`** — how long a VU that is being dropped may keep running the iteration it is already in. Defaults to **`30s`**. No other k6 executor has this key. Its validation is just as strict: `stages` needs at least one entry (*"at least one stage has to be specified"*), every entry needs both fields (*"stage N doesn't have a duration"*, *"stage N doesn't have a target"*), and either `startVUs` or one of the targets must be greater than 0. ## The two, side by side | | `constant-vus` | `ramping-vus` | |---|---|---| | where the VU count comes from | `vus`, default `1` | `startVUs`, default `1`, then each stage's `target` | | how long the scenario runs | `duration`, required, at least `1s` | the sum of every `stages` entry's `duration` | | wind-down of dropped VUs | not applicable — the count never falls mid-scenario | `gracefulRampDown`, default `30s` | | shape of the VU curve | flat | piecewise linear | | root-option shortcut | `vus` plus `duration` | `stages` | ## Neither executor tolerates the other's keys k6 decodes a `scenarios` entry with unknown fields disallowed, so a key the chosen executor does not define is a **fatal configuration error**: k6 stops before the first request with the invalid-config exit code rather than warning and carrying on. In practice: 1. `stages` inside a `constant-vus` entry fails — the fixed executor has no ramp of any kind. 2. `duration` inside a `ramping-vus` entry fails — its length is the sum of the stage durations and cannot be capped by a separate key. 3. `vus` inside a `ramping-vus` entry fails — the starting count is spelled `startVUs`. 4. `gracefulRampDown` inside a `constant-vus` entry fails — nothing ramps down there. That strictness is deliberate: a mistyped scenario key stops the run instead of silently changing its shape. Note the asymmetry, because it catches people out — an unknown key at the **root** of the exported `options` object only logs *"There were unknown fields in the options exported in the script"* and the run continues; the fatal behaviour is specific to keys inside a scenario entry. ## Picking one Use `constant-vus` when the VU count is one number for the whole scenario — it is two keys, and the count you configure is the count that runs from the first second to the last. Use `ramping-vus` when the count has to move during the scenario, in either direction, or more than once. Because both live in the same `scenarios` map with the same shared keys, moving from one to the other is a matter of swapping the `executor` string and its two or three own keys.
- What is the shortest duration a k6 constant-vus scenario will accept?One second. k6 validates the executor's `duration` against a `1s` floor, so `duration: '500ms'` fails with "the duration must be at least 1s", and omitting it fails with "the duration is unspecified". The floor is on the executor's own `duration` only — a `ramping-vus` stage is allowed `duration: '0s'`, which k6 treats as an instantaneous jump to that stage's target.
- When does k6 initialize the VUs for a constant-vus scenario?All of them up front. `constant-vus` plans its full `vus` count at time offset 0, so k6 initializes every VU before the scenario's first iteration and holds them for the whole `duration`. Nothing is added mid-run, because the count is fixed for the life of the scenario.
saying these in an interview costs you the question
- Says a constant-vus entry can carry a stages array
- Thinks ramping-vus takes a duration key of its own
- Calls vus the peak count for ramping-vus instead of startVUs
- Assumes an unknown key in a scenario entry is silently ignored
- Believes gracefulRampDown applies to constant-vus as well