What do k6's --no-setup and --no-teardown flags change for a script whose default function reads data?
answer
- two booleans, not two no-ops
- the stage is not entered at all
- the argument disappears with it
- undefined, then a TypeError
- archiving never runs either stage
basics
~10 sThey skip the stage entirely. With --no-setup k6 stores no setup data, so the default function receives undefined and any property read on it throws a TypeError on the first iteration.
solid answer
~40 s`--no-setup` and `--no-teardown` set the `noSetup` and `noTeardown` options, which also read from `K6_NO_SETUP` and `K6_NO_TEARDOWN` and are both `false` by default. They skip the function rather than making it a no-op, and that takes the data channel with it: with `--no-setup` the runner holds no bytes, every VU gets `undefined`, and a script written as `data.token` dies on its first iteration. `--no-teardown` is safer, since the VU work has already happened, and it is the usual way to leave a seeded environment standing for inspection. `k6 archive` accepts both flags and bakes the resulting option into the archive's stored options, but it never executes either function anyway.
code
bash · 9 lines# Skips setup() entirely: every VU is handed undefined.
k6 run --no-setup script.js
# Same switch through the environment, for a fixed CI invocation.
K6_NO_TEARDOWN=true k6 run script.js
# Bakes noSetup:true into the archive's stored options.
# Archiving itself runs only the init stage - it never calls setup().
k6 archive --no-setup -O bundle.tar script.jsgo deeper
Know that the two flags skip the stages outright, and that skipping setup means the default function's argument is undefined rather than an empty object.
Explain the mapping to the noSetup and noTeardown options and their K6_* variables, and why skipping setup breaks a script that reads the argument unguarded.
Use them deliberately: --no-teardown to leave a failed run's environment standing for inspection, --no-setup to shorten the loop against state that already exists.
Decide whether scripts must stay runnable with the stages skipped at all, since that guarantee shapes how VU code reads its credentials and how archives are shared.
## What the flags actually do k6 exposes two booleans for the lifecycle stages: - **`--no-setup`** sets the `noSetup` option, also readable from `K6_NO_SETUP`. - **`--no-teardown`** sets the `noTeardown` option, also readable from `K6_NO_TEARDOWN`. Both default to `false`. The important word is **skip**: k6 does not call the function and pretend it returned nothing useful, it never enters the function at all. Nothing in `setup()` executes — no login, no fixture creation, no reachability check. Note the asymmetry with the duration options in the same family: the booleans have command-line flags, while `setupTimeout` and `teardownTimeout` do not and must come from the script, a `K6_*` environment variable, or a `--config` file. ## The data channel goes with the stage This is the consequence people miss. Skipping `setup()` does not just skip its side effects; it removes the value every VU depends on. - With `--no-setup`, the runner holds no encoded bytes at all. - Every VU is therefore handed `undefined` as its argument, exactly as if the script had no `setup()` export. - A default function written as `` `Bearer ${data.token}` `` throws a `TypeError` on the very first iteration, and keeps throwing on every one after it. So `--no-setup` is only usable on a script whose VU code either does not read the argument or guards it. If you want the flag to be a real option for your team, write the VU code defensively — read the credential from `__ENV` when `data` is missing, for instance — rather than assuming the flag is a harmless switch. `--no-teardown` is far tamer, because by the time teardown would run, all the measured work is finished. It costs you cleanup, not results. ## When each flag earns its place | Situation | Flag | Why | |---|---|---| | Debugging VU code against an already-seeded environment | `--no-setup` | avoids re-minting credentials on every quick run | | Wanting the environment left standing for inspection after a failure | `--no-teardown` | the fixtures and sessions survive the run | | Re-running a script whose `setup()` is slow but idempotent | `--no-setup` | skips the expensive stage once the state exists | | Chaining several runs against one seeded fixture | `--no-teardown` on all but the last | only the final run cleans up | The recurring theme is a **shortened feedback loop during development**. In CI you almost always want both stages: the run should provision what it needs and leave nothing behind. ## What travels into a `k6 archive` `k6 archive script.js` bundles a test into a self-contained tar file — the script, its imported modules, its `open()`ed data files, and the **consolidated options**. Two facts about this stage matter here: 1. **`k6 archive` never runs `setup()`.** It executes only the init stage, which is what it needs in order to discover which files to bundle. No setup data is ever produced, and none is stored in the tar. 2. **`k6 archive` accepts the same option flags `k6 run` does.** Running `k6 archive --no-setup -O bundle.tar script.js` writes `noSetup: true` into the archive's stored options, so a later `k6 run bundle.tar` skips `setup()` unless something overrides it. That second point is the trap: the flag looks like it applies to the archiving step, and it does not — archiving runs no lifecycle function to skip. What it does is set a persistent option inside a file you may hand to someone else. Overriding it back at run time is possible, since CLI flags and `K6_*` variables both outrank options stored with the script. ## Setting them without the command line Because they are ordinary options, both are settable from the environment, which is the usual route in CI where the k6 invocation is fixed: - `K6_NO_SETUP=true k6 run script.js` - `K6_NO_TEARDOWN=true k6 run script.js` A run started this way is indistinguishable from one started with the flags. Because CLI flags and `K6_*` variables both outrank options written in the script, either route also overrides a `noSetup: true` that a script — or an archive — carries with it. ## What the flags do not do Three things people expect of them, and none of them happens: - **They do not neutralise the function.** k6 never enters it, so a `setup()` that also logs, or asserts a precondition, contributes nothing at all. - **They do not substitute a value.** There is no empty-object fallback; the argument is `undefined` and stays that way. - **They do not affect the run's verdict machinery.** Metrics, thresholds and the end-of-test output all behave exactly as they would otherwise; only the stage is missing. The clean mental model is that the pair turns the stage off at the scheduler, before anything script-level is consulted. Everything downstream then behaves precisely as it would for a script that never exported the function in the first place — which is also the easiest way to test whether your script tolerates the flag: delete the export locally and see whether the run still starts.
- Does k6's --no-setup make setup() return an empty object instead?No. The function is never entered, so no value is produced and no bytes are stored. Every VU and teardown receive `undefined`, which is why unguarded property access fails immediately.
- Does k6 archive run setup() when it builds the tar file?No. Archiving executes only the init stage, which is all it needs to discover the imported modules and opened data files. No setup data is produced or stored, so the archive is a bundle of code and options, never of results.
saying these in an interview costs you the question
- Thinks --no-setup makes setup() return an empty object
- Assumes --no-setup is safe on a script that reads data.token
- Believes k6 archive executes setup() to embed its result
- Looks for the flags as script options only, missing K6_NO_SETUP
- Thinks --no-teardown discards the run's metrics too