In k6, which five layers can set the same option, and in what order do they override each other?
answer
- five places, one winner
- defaults sit at the bottom
- the file layer is second lowest
- typed flags outrank K6_ variables
basics
~10 sk6 resolves each option through five layers, lowest to highest: built-in defaults, a JSON config file, the script's exported options, a K6_ environment variable, then the command-line flag. The CLI flag always wins.
solid answer
~40 sk6 builds one consolidated options object per run by merging five sources in a fixed order: built-in defaults, the JSON configuration file (`--config` / `-c`, or the default `config.json`), the script's exported `options`, `K6_*` environment variables, and finally the flags on `k6 run`. The merge is per key, not per layer — with `duration: '5m'` in the script and `--duration 30s` on the command line the run lasts 30 seconds, but `vus: 20` from the script still stands because nothing above it set `vus`. Only flags actually typed take part; an untouched `--vus` is not the same as `--vus 1`. A malformed `K6_*` value is a configuration error and exits 104 rather than being ignored.
code
javascript · 11 lines// script.js
import http from 'k6/http';
export const options = {
vus: 20,
duration: '5m',
};
export default function () {
http.get('https://quickpizza.grafana.com/');
}go deeper
Memorise the direction: the command line beats the environment, which beats the script, which beats the config file, which beats the defaults. Being able to say which of two conflicting settings wins is the whole screening question.
Explain that the merge is per key, so one run can take duration from a flag and vus from the script, and that only flags actually typed take part in the merge at all.
Show that you check all five layers before blaming a script — the default config.json is read on every run, and a bad K6_ value fails the run with exit code 104 rather than falling back.
Argue about which layer each setting should live in for a team, given that the top two layers cannot express a scenarios map and leave nothing behind in the repository.
## What k6 actually resolves before a run starts Before a single virtual user starts, k6 builds **one consolidated options object** for the run. It does not pick a winning source and read everything from it. Instead it merges five sources in a fixed order, and for **each key independently** the last source that supplied a value wins. That merge happens in `getConsolidatedConfig`, and the order is the same for `k6 run` and `k6 cloud run`. ## The five layers, lowest to highest 1. **Built-in defaults** — what k6 uses when nothing else says anything. 2. **The JSON configuration file** — the file at the default path, or whatever `--config <path>` / `-c <path>` names. 3. **The script's exported `options` object** — `export const options = { ... }` in the test file. 4. **`K6_*` environment variables** — one variable per option key, e.g. `K6_DURATION`, `K6_VUS`, `K6_RPS`. 5. **Command-line flags on `k6 run`** — `--duration`, `--vus`, `--rps`, and friends. | # | Layer | How you set it | Example | |---|---|---|---| | 1 | Defaults | nothing to do | one iteration in one VU | | 2 | Config file | `k6 run -c options.json script.js` | `{"duration": "10m"}` | | 3 | Script options | `export const options = {...}` | `duration: '5m'` | | 4 | Environment variable | `K6_DURATION=2m k6 run script.js` | `K6_DURATION=2m` | | 5 | CLI flag | `k6 run --duration 30s script.js` | `--duration 30s` | **The command line is always the top layer.** Nothing in a script or a config file can outrank a flag someone typed. ## A duration set four ways at once Take one `duration` and set it in all four settable layers of a k6 v2 run: - `options.json` (loaded with `-c`) says `"vus": 5, "duration": "10m"` - `script.js` exports `{ vus: 20, duration: '5m' }` - the shell sets `K6_DURATION=2m` - the command adds `--duration 30s` k6 runs for **30 seconds**, because the CLI flag is layer 5. The interesting part is `vus`: nobody set it at layer 4 or 5, so it keeps the script's `20`, not the config file's `5`. One run therefore takes its duration from the command line and its VU count from the script — that is what "resolved per key" means in practice. Follow the same `duration` value down the chain and you can see each layer take its turn. The file's `10m` replaces the default; the script's `5m` replaces the file's; `K6_DURATION=2m` replaces the script's; and `--duration 30s` replaces that. Nothing is averaged, combined or remembered — each layer either supplies a value for a key or stays out of the way entirely, and a layer that stays out of the way is indistinguishable from a layer that does not exist. ## Reading back what a run resolved You do not have to reason about the chain from the outside. The `k6/execution` module exposes `exec.test.options`, the consolidated and derived options as k6 finally built them, so a test can log or assert on the values that actually took effect: - `exec.test.options.duration` shows which layer won for the duration. - `exec.test.options.scenarios` shows the workload k6 derived from whichever layer supplied it. - Warnings printed at the start of the run name any layer that overrode a lower one. That is the only layer-proof answer to "what did this run really use", because nothing on the command line records the environment, and nothing in the environment records the config file. ## Details that decide real arguments - **Only flags you actually typed count.** k6 checks whether each flag was *changed*, not what its help text default is. Leaving `--vus` off is not the same as passing `--vus 1`; an untouched flag simply does not take part in the merge. - **`K6_*` names are a mechanical transform of the option key.** `discardResponseBodies` becomes `K6_DISCARD_RESPONSE_BODIES`, `blockHostnames` becomes `K6_BLOCK_HOSTNAMES`, `noConnectionReuse` becomes `K6_NO_CONNECTION_REUSE`. - **A malformed value at the environment layer is fatal, not ignored.** `K6_DURATION=fails k6 run script.js` fails while consolidating configuration and exits **104** (invalid config). It does not silently fall back to the script's value. - **The config-file layer is read on every run**, even when you never pass `--config` — k6 looks for `config.json` in the `k6/` folder inside the operating system's configuration directory. - **Not every option exists in every layer.** `scenarios`, for instance, has no `K6_*` variable and no CLI flag, so layers 4 and 5 simply cannot express it. ## The one place the merge is not key-by-key `duration`, `iterations`, `stages` and `scenarios` are treated as a single **execution group**. If a higher layer sets any one of the four, k6 first clears all four from the layers below it, then applies the new value. Every other key — `vus`, `rps`, `thresholds`, `userAgent`, `tags` — merges independently in the plain last-wins way described above. That single exception is why a `--duration` flag can wipe out a `stages` array or a whole `scenarios` map that a reviewed script declared.
- If a k6 option is left out of every layer, where does its value come from?From k6's built-in default, which is layer 1. With no execution option set anywhere, k6 falls back to a single `default` scenario running one iteration in one VU. Note that a flag's help-text default is not the same thing: k6 only merges flags you actually typed, so an unused `--vus` contributes nothing to the merge.
- How are K6_ variable names derived from option keys in k6?They are the option key upper-snake-cased with a `K6_` prefix: `duration` becomes `K6_DURATION`, `discardResponseBodies` becomes `K6_DISCARD_RESPONSE_BODIES`, `blockHostnames` becomes `K6_BLOCK_HOSTNAMES`, `noConnectionReuse` becomes `K6_NO_CONNECTION_REUSE`. A few keys have no variable at all — `scenarios` and `ext` among them — so the mapping is not total.
- Does k6 stop at the first layer that sets an option, or keep going?It applies every layer in order, lowest to highest, so the last one to supply a value wins for that key. Layers are not short-circuited and are not chosen wholesale: one run can take `duration` from the command line and `vus` from the script at the same time.
saying these in an interview costs you the question
- Thinks the script's exported options always win over flags
- Believes a K6_ variable overrides a command-line flag
- Says a malformed K6_DURATION value is ignored rather than fatal
- Puts the config file above the script in the order
- Cannot name the default config.json layer at all
- Assumes an unused --vus flag still contributes its help-text default