In k6, what does a script that declares only root-level `vus` and `duration`, with no `scenarios`, actually run?
answer
- there is no simple mode
- sugar, expanded before the run
- the derived scenario has a fixed name
- shortcuts and the map are mutually exclusive
basics
~10 sk6 always runs a scenarios map. When a script sets only root-level shortcuts, k6 derives one scenario named default from them: duration plus vus becomes constant-vus, stages becomes ramping-vus, and iterations becomes shared-iterations.
solid answer
~40 sThere is no separate "simple mode" in k6 v2 — the executor machinery is the only thing that runs a test. Before execution, k6 turns the root-level shortcut options into a one-entry `scenarios` map whose single key is `default`. `duration` (with `vus`) becomes a `constant-vus` scenario; `stages` becomes `ramping-vus`; `iterations` becomes `shared-iterations`, with `duration` reinterpreted as that executor's ceiling; a script with nothing at all becomes `per-vu-iterations` with one VU and one iteration. That is why a summary you never configured still shows a scenario called `default`. The shortcuts and an explicit `scenarios` map are mutually exclusive: writing `duration` and `scenarios` in the same script fails with *using `duration` and `scenarios` options simultaneously is not allowed* and exits 104.
code
javascript · 16 lines// These two option blocks configure exactly the same run.
export const shortcutForm = {
vus: 10,
duration: '30s',
};
export const expandedForm = {
scenarios: {
default: {
executor: 'constant-vus',
vus: 10,
duration: '30s',
},
},
};go deeper
Remember that k6 always runs scenarios, and that a script with only vus and duration still produces one, named default. Seeing that name in a summary you never wrote is normal.
Explain the mapping: duration gives constant-vus, stages gives ramping-vus, iterations gives shared-iterations, and nothing gives per-vu-iterations with one VU and one iteration.
Show why mixing a shortcut with an explicit scenarios map is a config error that exits 104 before traffic, and rewrite a shortcut script as its equivalent one-entry map before extending it.
Decide what a team's scripts standardise on. An explicit map costs a few lines but makes every later addition — a second workload, a stagger, per-scenario tags — additive instead of a rewrite.
## There is only one execution model k6 v2 has exactly one way of scheduling work: a map of scenarios, each driven by an executor. The familiar short form — `export const options = { vus: 10, duration: '30s' }` — is not a second, simpler engine. It is **sugar that k6 expands into a scenarios map before the run starts**, in a step k6 calls *deriving scenarios from shortcuts*. Understanding the expansion explains three things at once: why an unconfigured run still reports a scenario called `default`, why some option combinations are rejected outright, and why the same script behaves differently depending on which single root key you happened to set. ## The expansion table The derived scenario is always given the name `default` (k6's `DefaultScenarioName` constant, the same string as the default exported function's name): | what the script sets at the root of `options` | derived executor | notes | |---|---|---| | `iterations` (optionally with `vus`, `duration`) | `shared-iterations` | the iterations are shared across the VUs, and `duration` becomes that executor's ceiling rather than a run length | | `duration` (optionally with `vus`) | `constant-vus` | the VUs loop for the whole duration | | `stages` (optionally with `vus`) | `ramping-vus` | `vus` becomes the starting VU count | | `vus` and nothing else | `shared-iterations` | the iteration count is set equal to `vus`, so each VU tends to run once | | nothing at all | `per-vu-iterations` | one VU, one iteration — the smoke-test default | ## The order the shortcuts are tested in k6 does not combine the shortcuts; it takes the **first** matching case and ignores the rest of the ladder: 1. `iterations` is set — build `shared-iterations`; 2. otherwise `duration` is set — build `constant-vus`; 3. otherwise `stages` is non-empty — build `ramping-vus`; 4. otherwise only `vus` is set — build `shared-iterations` with that many iterations; 5. otherwise a `scenarios` map is present — leave it exactly as written; 6. otherwise — build `per-vu-iterations` with one VU and one iteration. ## The combinations k6 refuses Because the shortcuts and the map describe the same thing, k6 rejects scripts that try to write both, rather than guessing a merge. Each of these is a configuration error that aborts before any traffic, with exit code **104**: - `iterations` together with `stages` — *using `iterations` and `stages` options simultaneously is not allowed*; - `iterations` together with `scenarios` — same shape of message; - `duration` together with `stages`, and `duration` together with `scenarios`; - `stages` together with `scenarios`; - a `duration` of zero or less, which k6 refuses as a run length. Two softer behaviours are worth knowing. Setting `stages: []` or `scenarios: {}` explicitly is not an error: k6 warns that the value was explicitly emptied and falls through to the one-VU, one-iteration default. And a root-level `vus` on its own, alongside a `scenarios` map, does not error — k6 logs that `vus` overrides the scenarios configuration and replaces the map with the derived `default` scenario. ## What you gain by writing the map yourself Once you write `scenarios` explicitly you get everything the derived form cannot express, because a derived scenario is a single entry with all shared keys left at their defaults: - more than one workload in one run, each with its own executor; - a `startTime` per workload, so they can be staggered instead of all starting at zero; - a `gracefulStop` per workload rather than the blanket 30 seconds; - an `exec` per workload, so different scenarios drive different exported functions; - per-scenario `env` and `tags`, so the results stay separable. A useful migration habit: start from the shortcut form, then write the equivalent one-entry map before adding the second entry. `{ vus: 10, duration: '30s' }` is exactly `{ scenarios: { default: { executor: 'constant-vus', vus: 10, duration: '30s' } } }`, and having it spelled out makes the second scenario a purely additive change. ## Reading the evidence in the output The run header names the derived scenario just like a hand-written one, so a script with no `scenarios` block still prints a line beginning `* default:` followed by the executor's own description. If that line says `per-vu-iterations` and `1 iterations for each of 1 VUs` when you expected load, the cause is almost always that the root option you set was not one k6 treats as a shortcut, or that it was overwritten before the derivation step ran.
- What runs if a k6 script exports an `options` object with no execution keys at all?k6 derives a `default` scenario using the `per-vu-iterations` executor with one VU and one iteration, so the script's default function runs exactly once. That is the behaviour behind `k6 run script.js` on a bare script, and it makes an unconfigured run a smoke test rather than a load test.
- Why does adding `duration` to a script that already has a `scenarios` map break the run?The two describe the same thing, so k6 refuses to guess a merge. During shortcut derivation it detects `duration` alongside `scenarios` and returns *using `duration` and `scenarios` options simultaneously is not allowed*, which is treated as invalid configuration and exits 104 before any request is made.
- Is `stages: []` in a k6 script the same as omitting `stages`?Almost. Both end up at the one-VU, one-iteration `per-vu-iterations` default, but an explicitly empty `stages` also logs a warning saying it was explicitly set to an empty value and that the script will run with 1 iteration in 1 VU. The warning exists because an empty array is usually an accident of code generation.
saying these in an interview costs you the question
- Thinking vus and duration bypass the executor machinery entirely
- Expecting duration and scenarios to merge instead of erroring
- Assuming root iterations produces per-vu-iterations rather than shared-iterations
- Believing the derived scenario has no name in the output
- Assuming a bare script runs forever rather than once