Performance
Back to all articles
performance

Load Testing with k6: A Practical Introduction

Performance360 Engineering2 min read

Most load testing failures don’t come from the tool — they come from testing the wrong thing. A single endpoint hammered at 1,000 requests per second tells you almost nothing about how your system behaves when real users move through a real checkout, login, or search flow.

Why we reach for k6

We’re tool-agnostic by design, but k6 has become our default starting point for a few concrete reasons:

  • Scripts are JavaScript, so the same engineers who write your test automation can write your load tests without learning a new DSL.
  • Thresholds are first-class. You define pass/fail criteria (p95 < 300ms, error rate < 1%) directly in the script, so a load test can fail a CI pipeline the same way a unit test does.
  • Output integrates cleanly with Grafana and Prometheus, which matters once load testing stops being a one-off event and becomes part of your release process.

Structuring around a journey, not an endpoint

A useful load test script mirrors what a real session looks like:

import http from 'k6/http';
import { sleep, check } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 50 },
    { duration: '5m', target: 50 },
    { duration: '2m', target: 0 },
  ],
  thresholds: {
    http_req_duration: ['p(95)<300'],
    http_req_failed: ['rate<0.01'],
  },
};

export default function () {
  const login = http.post(`${__ENV.BASE_URL}/login`, { user: 'test', pass: 'test' });
  check(login, { 'logged in': (r) => r.status === 200 });

  sleep(1);

  const search = http.get(`${__ENV.BASE_URL}/search?q=laptop`);
  check(search, { 'search ok': (r) => r.status === 200 });

  sleep(2);
}

Notice there’s no single hardcoded target — stages ramps up, holds, and ramps down, which is what actually reveals capacity limits rather than just confirming a server can survive a fixed burst.

Where this leads

A load test that mirrors real usage is also a load test whose results are actionable: when p95 breaks, you know exactly which step in the journey degraded, not just that “the API is slow.” That’s the difference between a number in a report and a finding an engineering team can act on.

We’ll follow this up with a piece on distributing k6 runs across regions to simulate realistic traffic geography — subscribe via RSS to get notified.