In a k6 shared-iterations scenario, what happens to iterations still unrun when maxDuration expires?
answer
- the ceiling wins over the budget
- ten minutes unless you say otherwise
- remainder is dropped, not deferred
- the run still ends normally
basics
~20 sThey are never run. k6 stops starting new iterations at maxDuration, which defaults to 10 minutes, lets in-flight ones finish within gracefulStop, and counts the unstarted remainder on the dropped_iterations metric. The run itself does not fail.
solid answer
~40 s`maxDuration` is a hard wall-clock ceiling on the scenario, defaulting to `'10m'` on both `shared-iterations` and `per-vu-iterations`. When it expires k6 stops handing out new iterations, gives in-flight ones up to `gracefulStop` to finish, and simply drops the remainder — there is no extension and no retry. The shortfall shows up on the built-in `dropped_iterations` counter, for which an expired `maxDuration` is one producing condition. Crucially the test still ends normally: overrunning the ceiling is not by itself an error, so a run that completed 400 of the 500 iterations you configured still prints a summary. Compare the summary's iteration count against your budget, and set `maxDuration` explicitly on any long budget.
code
javascript · 12 linesexport const options = {
scenarios: {
// 500 rows at ~3s each on 2 VUs needs ~750s, so the
// implicit 10m ceiling would stop it after 400 rows.
csv_pass: {
executor: 'shared-iterations',
vus: 2,
iterations: 500,
maxDuration: '30m',
},
},
};go deeper
Remember two facts: maxDuration defaults to 10 minutes on the iteration executors, and iterations that have not started when it expires are simply never run. Set it explicitly whenever the budget is large.
Explain the sequence — new iterations stop at maxDuration, in-flight ones get gracefulStop on top, the remainder is dropped and counted. Be clear that the scenario also ends early whenever the budget is spent first.
Show how you would catch a silent shortfall in a real run: compare the summary iteration count against the configured budget, look for dropped_iterations, and size maxDuration from a measured per-iteration cost rather than guessing.
The judgment call is whether maxDuration is a safety net or a contract. Decide whether a scenario that hits its ceiling gets a larger maxDuration, a smaller iterations budget, or is simply accepted, and write that choice into the scenario rather than leaving it implicit.
## `maxDuration` is a ceiling, not a target Both of k6's iteration-budget executors — `shared-iterations` and `per-vu-iterations` — take a `maxDuration` key alongside `vus` and `iterations`. It is the maximum wall-clock time the scenario may occupy, and **it defaults to `'10m'`** when you leave it out. k6 rejects any value below one second. The key exists because an iteration budget has no inherent time bound: 500 iterations take as long as they take. `maxDuration` is the guard rail that stops a slow system under test from turning a five-minute run into an hour-long one. ## What happens the moment it expires 1. k6 stops handing out new iterations. In `shared-iterations` the VUs stop claiming numbers from the shared counter; in `per-vu-iterations` each VU breaks out of its own loop. 2. Iterations already in flight are given up to `gracefulStop` (30 seconds unless changed) to finish normally. `maxDuration` excludes that grace window — the hard deadline is `maxDuration + gracefulStop`. 3. The unstarted remainder is **never run**. There is no extension, no retry, and no automatic bump in VU count to catch up. 4. k6 records the shortfall on the built-in `dropped_iterations` counter: an expired `maxDuration` is one of the conditions that produces it in these two executors. 5. The scenario then ends and the test finishes normally. Overrunning `maxDuration` is not, in itself, an error — the run does not fail because of it. That last point is what makes this dangerous. A run that silently did 400 of the 500 iterations you asked for still prints a summary and still exits the way it otherwise would. ## Worked example: 500 CSV rows, 100 of them never touched Take the data-driven pass over a 500-row CSV, configured as `shared-iterations` with `vus: 2`, and suppose each iteration takes about three seconds against the target system. | quantity | value | |---|---| | iterations requested | 500 | | concurrency | 2 VUs | | time per iteration | ~3 s | | wall clock actually needed | 500 / 2 x 3 s = 750 s | | default `maxDuration` | 600 s | | iterations completed | 2 VUs x 200 = 400 | | CSV rows never touched | 100 | Nothing in that configuration is invalid, so k6 accepts it, runs it, and stops it 150 seconds early. If you were relying on the pass to exercise every row, a fifth of the file was skipped. ## How to spot it and what to change - Compare the `iterations` count in the end-of-test summary against the budget you configured; a shortfall is the direct symptom. - Check whether `dropped_iterations` appears at all — in an iteration-budget scenario it means the clock ran out. - k6 also warns *No script iterations fully finished, consider making the test duration longer* in the extreme case where not a single iteration completed. - Check which executor the scenario uses before doing the arithmetic: the budget that had to fit inside the ceiling is `iterations` under `shared-iterations`, but `vus * iterations` under `per-vu-iterations`. The fixes are all configuration-side: 1. Raise `maxDuration` explicitly to something you have measured against, for example `maxDuration: '30m'` for the 500-row pass above. Never leave it implicit on a long budget. 2. Raise `vus` so the same budget is spent inside the same ceiling — with `shared-iterations` this changes only the elapsed time, because the total stays pinned at `iterations`. 3. If the budget genuinely does not fit, shrink it. A 200-row sample that completes beats a 500-row pass that stops at 400, because you know exactly what the smaller one covered. A useful habit is to make the ceiling an explicit multiple of the time you actually measured rather than a round number: if 500 iterations at two VUs took 750 seconds on a good day, `maxDuration: '30m'` leaves room for a bad one while still cutting off a run that has gone genuinely wrong. ## Two things `maxDuration` is not - It is **not** a run length. The scenario ends the instant the iteration budget is spent, however much of `maxDuration` is left. It only ever cuts a run short. - It is **not** the same key as a root `duration`. On the arrival-rate and VU-count executors `duration` is the length of the run; on `shared-iterations` and `per-vu-iterations` there is no `duration` key at all, only `maxDuration`. The one bridge is the root shortcut: a root `duration` set alongside a root `iterations` is copied into the derived `shared-iterations` scenario's `maxDuration`.
- Does a k6 scenario always run for its whole maxDuration?No — it is a ceiling, never a target. `shared-iterations` and `per-vu-iterations` end the instant their iteration budget is spent, however much of `maxDuration` remains. The key can only cut a run short; it can never extend one.
- How does maxDuration relate to gracefulStop in a k6 iteration-budget scenario?`maxDuration` excludes `gracefulStop`. New iterations stop being started at `maxDuration`; iterations already running get up to `gracefulStop` (30 seconds unless set otherwise) to finish, so the hard deadline for the scenario is `maxDuration + gracefulStop`.
- Why is there no duration key on k6's shared-iterations executor?Because the scenario is bounded by a count, not a clock — `duration` belongs to the executors whose stopping condition is time. The only bridge is the root shortcut: a root `duration` set alongside a root `iterations` is copied into the derived scenario's `maxDuration`.
saying these in an interview costs you the question
- Thinks k6 extends the scenario until the iteration budget finishes
- Expects the leftover iterations to be retried or carried over
- Assumes an expired maxDuration makes the k6 run fail by itself
- Treats maxDuration as the scenario's run length rather than a ceiling
- Believes maxDuration is unset by default on iteration executors
- Says gracefulStop time counts inside the maxDuration window