Every engineering team believes their system is fast — until real traffic proves otherwise. Load testing is the practice of finding performance limits, bottlenecks, and degradation patterns in a controlled environment before users encounter them in production. Done right, it shifts performance problems from incidents into boring line items on a backlog.
What changed in 2026
- k6 (now under Grafana Labs) became the dominant scripting-based tool. Its low-overhead binary, JavaScript/TypeScript test scripts, and native CI integration replaced JMeter for most modern stacks.
- AI-assisted test generation arrived. Feeding an OpenAPI spec or a HAR file to a model to generate a realistic load script is now a practical first step, not science fiction.
- Cloud-native load generation is cheap. Distributed load generation that previously required a dedicated cluster can now be orchestrated on-demand via k6 Cloud, Artillery Cloud, or self-managed k6 operators on Kubernetes.
- Shift-left performance testing grew. Micro-benchmarks run in CI on every PR catch regressions before they compound.
Types of load tests
| Test type |
What it measures |
When to run |
| Smoke test |
Does it work at 1–2 users? |
On every deploy |
| Load test |
Normal expected traffic |
Pre-launch, weekly |
| Stress test |
Beyond expected traffic to find the ceiling |
Monthly or pre-event |
| Spike test |
Sudden burst (10× in seconds) |
Before flash sales, viral events |
| Soak test |
Sustained normal load for hours |
Catch memory leaks, connection pool exhaustion |
Run smoke and load tests in CI. Save stress and soak tests for scheduled runs in staging.
A k6 script that tests a realistic journey
// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Rate } from 'k6/metrics';
const errorRate = new Rate('errors');
export const options = {
stages: [
{ duration: '2m', target: 50 }, // ramp up
{ duration: '5m', target: 50 }, // hold
{ duration: '2m', target: 100 }, // ramp to stress
{ duration: '5m', target: 100 }, // hold
{ duration: '2m', target: 0 }, // ramp down
],
thresholds: {
http_req_duration: ['p95<500'], // 95th percentile under 500ms
errors: ['rate<0.01'], // less than 1% error rate
},
};
export default function () {
// Search
const searchRes = http.get(`${__ENV.BASE_URL}/api/products?q=widget`);
check(searchRes, { 'search ok': (r) => r.status === 200 });
errorRate.add(searchRes.status !== 200);
sleep(1);
// Add to cart
const cartRes = http.post(
`${__ENV.BASE_URL}/api/cart`,
JSON.stringify({ productId: 'abc123', qty: 1 }),
{ headers: { 'Content-Type': 'application/json' } }
);
check(cartRes, { 'cart ok': (r) => r.status === 201 });
sleep(Math.random() * 2);
}
The thresholds block turns the test into a pass/fail check you can enforce in CI.
How to start
- Define success criteria first. Without thresholds (p95 < 500ms, error rate < 1%) you have no pass/fail signal, just graphs.
- Write a smoke test. One virtual user, one minute. If smoke fails, something basic is broken before you even attempt load.
- Build realistic scenarios from real traffic. Pull a sample of production request logs or a HAR file; replay the real user mix, not just your homepage.
- Run against a prod-like staging environment. Different instance sizes, shared databases, and missing caches will give you useless numbers.
- Add CI thresholds. Run a 5-minute load test in CI on merges to main. Fail the build if p95 latency regresses by more than 20%.
Common mistakes
Testing the wrong endpoints. Homepage and /health are fast. The slow paths are checkout, search with filters, and report generation. Test the paths that cost you money when they fail.
No baseline. Running a stress test without a previous baseline means you can't tell if performance improved or regressed after a change.
Load testing production directly. If you must test prod, use gradual ramp-up, real-time monitors, and a rollback plan. Never run a spike test against production without alerting your ops team.
Ignoring the database. Application servers are horizontally scalable; the database usually isn't. Load tests that don't include DB query profiling miss the most common bottleneck.
Single-region load generators. Generating 50,000 RPS from one machine in one data center is not the same as 50,000 RPS from real distributed users. Distribute your load generators.
What to skip
- JMeter for greenfield projects. The XML config format and GUI-first workflow are friction. k6 or Locust are faster to iterate on for most modern teams.
- Soak testing too early. A soak test only teaches you something useful once your smoke and load tests are clean. Fix the obvious problems first.
- Load testing without profiling. Raw numbers tell you there is a problem; profiling (flame graphs, slow query logs) tells you where. Always pair load tests with profiling tools.
FAQ
How many virtual users should I simulate?
Start with your expected peak concurrent sessions, then scale to 2–3× that to find headroom. Don't start at 10× without understanding your baseline — you'll just generate noise.
What is the difference between concurrency and requests per second?
Concurrency is simultaneous active connections; RPS is requests completed per second. They are related but not the same — a high-latency endpoint can have high concurrency with low RPS. Track both.
Should I load test third-party APIs?
Never load test a third-party API you don't own. Mock or stub external dependencies in your load tests. You will violate their terms of service and possibly cause an incident for other customers.
How do I fix a performance regression found in load testing?
Identify the bottleneck first — database queries, CPU, memory, or a specific code path — using profiling. Optimize the specific bottleneck, retest, and verify the regression is gone before merging.
Where to go next
Observability vs monitoring in 2026, CI/CD pipeline basics in 2026, and Caching strategies in 2026.