In k6, exactly what makes a check() call return false rather than true?
answer
- one falsy entry is enough
- nothing short-circuits, all entries run
- JavaScript truthiness, not a boolean check
- an empty set returns true
basics
~10 sA k6 check() call returns false when any entry in its set evaluates to a JavaScript falsy value. Every entry is still evaluated and recorded, and an empty set returns true because nothing failed.
solid answer
~40 sIn k6 v2 the return starts as `true` and is set to `false` by the first entry whose value is falsy — but evaluation does not stop there. k6 walks every key in the set, evaluates it, records it, and only then returns the accumulated boolean, so a three-entry call with one failure still reports all three results. The judgement is plain **JavaScript truthiness**, not a boolean type check: `0`, `''`, `false`, `null` and `undefined` fail, while `1`, `-1` and *any* non-empty string pass — including the string `'false'`. The corollary catches people out: a set with no entries has nothing falsy in it, so `check(res, {})` returns `true` while verifying nothing.
code
javascript · 15 linesimport { check } from 'k6';
// stand-in for a parsed login response body
const body = { token: '', error: 'false' };
export default function () {
const ok = check(body, {
'login token is present': (b) => b.token, // '' is falsy -> fails
'login reported no error': (b) => b.error, // 'false' is a NON-EMPTY string -> passes
});
console.log(ok); // false: one falsy entry is enough
console.log(check(body, {})); // true: an empty set has nothing to fail
}go deeper
Remember the shape of the answer: true only if every named condition passed, false if any of them did not. Assign it to a variable and log it once to see it for yourself.
Explain the two mechanics beneath that: truthiness coercion rather than a boolean type check, and the absence of short-circuiting so every entry is still evaluated and recorded.
Show the failure modes you have actually hit — a 'false' string passing, a dynamically built empty set going permanently green — and how you keep predicates side-effect free.
Argue about what the returned boolean is for: a suite that ignores it and one that branches on it to skip dependent requests produce very different results from the same failing dependency.
## The rule, in one line k6's `check()` returns `true` only when **every** entry in its set evaluated to a truthy value. A single falsy entry makes the whole call return `false`. That is the entire contract of the return value. It is not a count, not a percentage, and not an error — it is one boolean summarising one call, and your script is free to ignore it. ## How each entry is judged For each key in the set, k6 produces a value — by calling the entry if it is a function, or taking it as it stands if it is not — and then applies **ordinary JavaScript truthiness** to it. There is no boolean type check, so a predicate that returns a string, a number or an object is perfectly legal and is judged the way JavaScript would judge it in an `if`. | entry evaluates to | verdict | |---|---| | `true`, `1`, `-1` | pass | | `'true'`, `'false'`, any non-empty string | **pass** | | `false`, `0`, `''` | fail | | `null`, `undefined` | fail | The third row is the one that costs people an afternoon. `'false'` is a non-empty string, so it is truthy, so the check passes. A predicate written as `(r) => r.json('error')` will pass whenever the API returns the *string* `"false"` in that field — the check is green and the assertion is meaningless. Convert explicitly: ```javascript check(res, { 'login reported no error': (r) => r.json('error') === false, }); ``` The same coercion applies whichever shape the entry takes, and there are three: - a **literal value** — `{ 'ok': res.status === 200 }`, evaluated by JavaScript before `check()` is entered; - a **zero-argument function** — `{ 'ok': () => res.status === 200 }`, closing over the value directly; - a **function taking the value** — `{ 'ok': (r) => r.status === 200 }`, the idiomatic form. All three are judged identically, because k6 resolves the entry to a value first and applies truthiness second. The shape you choose changes *when* the work happens, never *how* the result is read. ## One failure does not stop the others k6 does **not** short-circuit. When an entry evaluates falsy, k6 records the failure, sets the pending return value to `false`, and moves on to the next key. Every entry in the set is evaluated and every entry produces a result. That matters in three practical ways: - **Diagnosis stays complete.** A login check that fails on status still tells you whether the body field was also missing, which is usually the difference between "the service is down" and "the contract changed". - **Side effects still happen.** If a predicate mutates state, logs, or parses an expensive body, that work is done even after an earlier entry failed. Keep predicates pure. - **Ordering is cosmetic.** Because nothing short-circuits, putting the cheapest condition first buys you nothing except readability. ## The empty-set trap Since the return starts as `true` and is only ever pushed down by a falsy entry, a set with **no entries at all** returns `true`. `check(res, {})` is a green, silent no-op. This is not a hypothetical. It is what you get when a set is built dynamically from a filter that matched nothing, or from an object spread that produced no keys. The same shape appears if the second argument is not an object with enumerable keys at all — a bare number, for instance, records nothing and reports success. The defence is to treat "how many conditions did this call actually contain?" as something worth asserting when the set is computed rather than written out literally. ## What the boolean is actually for The return value exists so your script can *branch*, and nothing more. Typical uses: 1. **Guard the rest of the iteration.** If a login response did not produce a token, there is no point issuing the five authenticated requests that follow; read the boolean and return early. 2. **Feed your own logging.** `if (!ok) console.error(res.body)` gives you the payload that failed, which the check name alone cannot carry. 3. **Extract a value only when it is safe.** Confirm the shape first, then parse. ```javascript const ok = check(res, { 'login status is 200': (r) => r.status === 200, 'login body has a token': (r) => typeof r.json('token') === 'string', }); if (!ok) { return; // skip the authenticated calls this iteration } ``` Ignoring the return value is also a legitimate choice — many scripts call `check()` purely to record the result and never look at the boolean. What is not legitimate is *assuming* the boolean did something on its own: it is a value, and a value that nothing reads changes nothing.
- Why does a k6 check predicate returning the string 'false' pass?Because k6 applies JavaScript truthiness to whatever the entry evaluates to, and `'false'` is a non-empty string. Only `false`, `0`, `''`, `null` and `undefined` fail. Compare explicitly — `(r) => r.json('error') === false` — rather than returning a raw field.
- If the second entry of a k6 check() set fails, is the third still evaluated?Yes. k6 evaluates and records every entry in the set; the falsy one only fixes the value the call will eventually return. That is deliberate — you get the full picture of what was wrong, not just the first thing that broke.
- What does check(res, {}) return in k6?`true`. The return value starts at `true` and is only lowered by a falsy entry, so a set with no entries has nothing to fail. It records no results either, which makes a dynamically built empty set a silent, permanently green no-op.
It behaves like a printed inspection checklist: the inspector ticks or crosses every line even after the first cross, and the certificate is issued only if no line was crossed. A blank checklist is issued a certificate too.
saying these in an interview costs you the question
- Thinks check() stops at the first failing entry
- Assumes predicates must return a real boolean
- Believes an empty check set fails or errors
- Says the returned false stops the iteration by itself
- Returns a raw response field instead of comparing it