k6 Scripting
k6 is a scriptable load testing tool where test logic is plain JavaScript and load profiles are declared in an options block. Learn the default function, checks, thresholds, executors and the CI-friendly exit code.
What is this?
k6 is a developer-focused, scriptable load testing tool built by Grafana Labs. Test logic is written in JavaScript: a default function that every virtual user executes, plus an options block that declares the load profile. It is Go-based under the hood, so a single instance can sustain tens of thousands of virtual users, and it ships with checks, thresholds and native JSON, InfluxDB and Prometheus output.
Why do you need it?
- Scripts are code: git-reviewable, reusable across teams and testable with plain assertions.
- Declarative thresholds fail a run when budgets are breached, giving you a built-in CI gate.
- Native HTTP/1.1, HTTP/2, WebSocket and gRPC support, and a single process scales to roughly 40k+ VUs.
- The CLI, REST API and .js scripts integrate with existing build systems without special daemons.
- Rich built-in metrics: http_req_duration, http_req_failed, vus, iterations and custom metrics.
Mental model
Test-driving a car on a course
k6 scripting
The default function is the lap the driver repeats; options are the course conditions (how many drivers, how fast they enter); checks are the flags waved on each lap; thresholds are the rules that decide whether the lap time qualifies.
How it works
- The default function is executed once per iteration by each virtual user — it is the workload your test applies.
- The options block configures load: executor (ramping-vus, constant-arrival-rate, shared-iterations), stages and thresholds.
- Checks validate per-response conditions and count passes and failures in the summary.
- Thresholds evaluate metric expressions after the run and set the process exit code (0 = pass, 1 = fail).
- Results stream to stdout, JSON, CSV, InfluxDB or Prometheus so you can build dashboards.
How a k6 run executes iterations and enforces thresholds.
Key commands
k6 run script.jsRun the script with the load profile declared in its options block.
default ✓✓ 120s 2000 iterations · 0 failed
k6 run --vus 100 --duration 5m script.jsOverride the load profile from the CLI without editing the script.
k6 run --iterations 1000 script.jsStop after a fixed number of iterations instead of a time window.
k6 run --out json=results.json script.jsStream every metric point to JSON for post-processing and storage.
k6 run --out influxdb=http://localhost:8086/k6 script.jsPush results directly to InfluxDB for dashboarding in Grafana.
k6 run --summary-trend-stats 'avg,p(50),p(90),p(95),p(99)' script.jsControl which percentiles the summary prints for trend metrics.
Examples
A k6 load test with checks and thresholds
import http from "k6/http"import { check, sleep } from "k6" export const options = { vus: 50, duration: "2m", thresholds: { http_req_failed: ["rate<0.01"], http_req_duration: ["p(95)<250", "p(99)<500"], },} export default function () { const res = http.get("https://api.example.com/v1/items?page=1", { headers: { Authorization: "Bearer token" }, tags: { endpoint: "list-items" }, }) check(res, { "status is 200": (r) => r.status === 200, "body is JSON": (r) => r.json() !== null, }) sleep(1)}Fifty virtual users hit the endpoint for two minutes. Every iteration checks the status code and that the body parses as JSON, then pauses one second. Thresholds encode the budget: the run fails if errors exceed 1% or p95 exceeds 250ms, and the non-zero exit code is what CI consumes.
In production
- k6 runs as a Kubernetes CronJob or GitHub Actions step, gating merges with the exit-code-based thresholds.
- Scripts share test logic through the JS module system so suites stay DRY across teams.
- Results stream to Prometheus or InfluxDB, and the same dashboards show perf tests and production trends side by side.
- Distributed load across regions coordinates k6 instances (or k6 Cloud) without managing agent fleets.
Best practices
- Keep the default function small and realistic — include think time (sleep) and varied payloads.
- Tag requests (tags: { endpoint, region }) so trend metrics can be sliced per endpoint in dashboards.
- Use thresholds as the single source of truth for pass/fail instead of eyeballing summary output.
- Pin the k6 version in CI so script semantics don't drift between runs.
- Store scripts in git with the same review bar as application code.
Common mistakes
- Forgetting sleep() and running a tight loop, which drives unrealistic server pressure.
- Relying on the default function alone and never using executors, so the load profile isn't expressible.
- Ignoring http_req_failed in thresholds — latency numbers are meaningless when errors are climbing.
- Running k6 from a laptop on Wi-Fi and blaming the app for client-side jitter.
Common issues
TypeError: Cannot read properties of undefined (reading 'status')The request threw before returning a response object, or the URL is unreachable.
Guard with if (res) before accessing fields, and confirm the URL with curl first.
threshold 'http_req_failed' has an invalid expressionA typo in the threshold expression.
Use valid expressions like rate<0.01 and p(95)<250, comma-separated inside the array.
dial tcp: lookup api.example.com: no such hostDNS failure or a wrong hostname.
Verify the hostname resolves with nslookup and is reachable with curl.
insufficient VUs, reached the max VU limitThe load profile requires more virtual users than k6 allows on the process.
Raise the limit with K6_VUS_MAX or scale out to multiple k6 instances / distributed load.
Debugging tips
- Run a single VU with --vus 1 -i 1 and log res.body to verify the script before scaling load.
- Use http_req_connecting and http_req_tls_handshaking trends to isolate network and TLS overhead from app latency.
- Slice thresholds per tag, e.g. http_req_duration{endpoint:list-items}, to isolate a slow path.
- Compare runs with and without connection reuse to measure its cost explicitly.
Interview checkpoint
Hands-on lab
- Write a k6 script with checks and realistic think time
- Tag requests by endpoint for sliceable metrics
- Declare thresholds for latency and error rate
- Run in headless mode and read the exit code as a CI gate
Build a CI-Ready k6 Test with Thresholds
35 min · intermediate
Quick quiz
Which part of a k6 script runs for every virtual user iteration?
A k6 run exits with a non-zero code if any threshold expression is violated.
You want a test that stops after exactly 10,000 requests, regardless of time. Which approach fits best?
Cheat sheet
k6 run script.jsExecute with options from the script--vus / --durationOverride load from the CLI--iterations NStop after N iterations--out json=out.jsonStream results to JSON--summary-trend-stats 'p(95)'Choose printed percentilesthresholdsDeclare pass/fail budgets