Executive Definition
Load testing is the subtype of performance testing that measures a system's behaviour at the expected peak of its production workload. Where general performance testing spans multiple shapes (stress, soak, spike), load testing is narrow: model the target traffic, apply it, and check whether latency, throughput, and error rate stay within SLOs. If they do, the system is ready for that peak; if they do not, capacity or code needs work before the peak arrives.
A useful load test starts from a workload model, not from an arbitrary requests-per-second number. The model captures who uses the system, in what proportions, and what they do. For an e-commerce site: 70% browsers, 20% add-to-cart, 8% checkout initiators, 2% checkout completers, with realistic think times between actions and a session distribution that matches production analytics. Get the model wrong and every conclusion is wrong.
Peak is a specific number, not a vague notion. Calculate it from production traffic: highest 5-minute window over the past 90 days, multiplied by a growth factor (typically 1.5–2×) to give headroom for the coming quarter. That number becomes the load-test target. Testing at 'a lot of traffic' with no reference to the actual peak produces numbers no one can act on.
Distributed execution is table stakes in 2026. A single load generator maxes out somewhere between 5,000 and 15,000 concurrent virtual users depending on scenario complexity. Beyond that you need k6 Cloud, Grafana Cloud k6, Locust with worker nodes, or a self-hosted Kubernetes runner distributing the load across many pods. Confirm the generators themselves are not saturated before trusting the numbers they report.
Load testing pays off when it becomes routine. A quarterly load run is better than none; a nightly critical-path load run against a stable perf environment catches regressions inside the same sprint that introduced them. Make the run boring: same scenario, same environment, same SLO thresholds, same report format, so the only signal your team has to interpret is the change from run to run.
Architecture & Production Code
A distributed load test coordinates many generators against one target environment while a central control plane aggregates results and enforces thresholds.
┌───────────────────┐
│ Workload model │
│ (analytics-based)│
└─────────┬─────────┘
│
▼
┌───────────────────┐ ┌────────────────────────┐
│ k6 test script │ ───────▶ │ Control plane │
│ (scenarios, VUs) │ │ (k6 Cloud / Operator) │
└───────────────────┘ └───────────┬────────────┘
│ fan-out
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Generator 1 │ │ Generator 2 │ │ Generator N │
│ (5,000 VUs) │ │ (5,000 VUs) │ │ (5,000 VUs) │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
└───────────────────┼───────────────────┘
▼
┌───────────────────┐
│ Perf environment │
│ (prod-shaped) │
└─────────┬─────────┘
▼
┌───────────────────┐
│ SLO gate & report │
└───────────────────┘Workload modelling is the least glamorous and most consequential step. Pull real analytics: user counts per hour, action distribution, session length, geographic mix. Encode that as scenarios with weights. A model that says '70% browse, 20% add-to-cart, 10% checkout' is worth a hundred perfectly written scripts pointing at the wrong distribution.
The control plane's job is to time-align generators, aggregate results, and enforce thresholds. Without it, N generators produce N reports and no one can tell whether p99 is 500ms or 5s across the whole run. k6 Cloud, Grafana k6 Operator, and Locust master/worker mode all provide this coordination.
The perf environment must be prod-shaped. Same instance sizes, same DB configuration, same third-party integrations (or realistic stubs at the same latency). Load-testing against a shrunk-down staging environment produces numbers that flatter the code and fail on release day.
// k6-peak-load.js
import http from "k6/http";
import { group, sleep } from "k6";
export const options = {
scenarios: {
browsers: {
executor: "ramping-vus",
exec: "browseFlow",
startVUs: 0,
stages: [
{ duration: "2m", target: 1400 },
{ duration: "10m", target: 1400 },
{ duration: "2m", target: 0 },
],
},
checkouters: {
executor: "ramping-vus",
exec: "checkoutFlow",
startVUs: 0,
stages: [
{ duration: "2m", target: 400 },
{ duration: "10m", target: 400 },
{ duration: "2m", target: 0 },
],
},
},
thresholds: {
"http_req_duration{scenario:browsers}": ["p(95)<400"],
"http_req_duration{scenario:checkouters}": ["p(95)<800", "p(99)<1500"],
"http_req_failed": ["rate<0.005"],
},
};
const BASE = __ENV.BASE_URL;
export function browseFlow() {
group("browse", () => {
http.get(`${BASE}/api/products?category=featured`);
sleep(3);
http.get(`${BASE}/api/products/SKU-42`);
sleep(6);
});
}
export function checkoutFlow() {
group("cart+checkout", () => {
const cart = http.post(`${BASE}/api/cart`, JSON.stringify({ sku: "SKU-42", qty: 1 }), {
headers: { "Content-Type": "application/json" },
});
sleep(2);
http.post(`${BASE}/api/checkout`, JSON.stringify({
cartId: cart.json("id"),
paymentToken: "tok_test_visa",
}), { headers: { "Content-Type": "application/json" } });
sleep(4);
});
}Load vs Stress vs Spike vs Soak
| Aspect | Load | Stress | Spike | Soak |
|---|---|---|---|---|
| Target load | Expected peak | Above breaking point | Sudden 5–10× jump | Expected sustained |
| Duration | 30–90 min | Until failure | Minutes | 8–72 hours |
| Primary metric | SLO compliance | Breaking point | Recovery time | Long-run stability |
| Environment | Prod-shaped | Prod-shaped | Autoscaling-enabled | Prod-shaped, monitored |
| When to run | Pre-release | Quarterly | Before promo / event | Quarterly |
| Common failure | SLO breach | Cascade failure | Autoscaler lag | Memory leak |
Load testing is the routine health check; stress, spike, and soak are targeted diagnostics. A team with only load testing will miss elasticity issues and slow leaks; a team with only stress testing will lack a baseline to compare against.
Production Debugging Scenarios
Load test results mislead in a few predictable ways. Rule these out before you conclude the code is at fault.
SLO passes locally, fails in the cloud runner
- Symptom
- Local k6 run reports p95 at 320ms; cloud runner reports 1.4s against the same environment.
- Root cause
- Cloud runner is geographically far from the target environment, adding network latency.
- Fix
- Place load generators in the same region as the target. Report and threshold on server-side timings (app metrics) as well as client-side.
Reported error rate is zero but users see failures
- Symptom
- k6 reports http_req_failed rate of 0%; APM shows 2% 500 errors.
- Root cause
- The scenario uses http.get(...) without a check(), so non-2xx responses count as successful HTTP requests.
- Fix
- Wrap every request in check() with status assertions. Feed those into a Rate metric and threshold it.
Second identical run produces very different numbers
- Symptom
- Consecutive runs show p95 of 400ms and 900ms with no code change.
- Root cause
- Shared perf environment had a cache flush, or a noisy-neighbour tenant on the same node.
- Fix
- Use a dedicated perf environment, warm caches with a fixed pre-run, and run each test three times, reporting the median.
Practice this concept in a real QA interview
Run a live mock with our AI Interview Coach, tune your resume with the ATS Resume Reviewer, and screen live listings on the QA Jobs Radar.