API Testing
Advanced 15 min read API Testing

API Performance Testing

API performance testing measures how an endpoint behaves under load: latency, throughput, error rate and resource use. Load tests ramp virtual users to find breakpoints, and soak tests hold steady load to catch slow degradation, producing defensible numbers instead of 'it felt fast.'

performance testingload testinglatencyp95k6throughput
Introduction

What is this?

API performance testing measures how an endpoint behaves under load: latency, throughput, error rate and resource use. Load tests ramp virtual users against endpoints to find breakpoints, and soak tests hold steady load to catch memory leaks and slow degradation. The goal is a number you can defend — p95 latency at N requests per second — instead of a gut feeling.

Motivation

Why do you need it?

  • Latency is customer-visible: every added 100ms of p95 directly affects conversions and retention.
  • Load tests find bottlenecks — slow queries, missing indexes, N+1 calls — before they become production incidents.
  • Capacity planning needs measured numbers to right-size instances and auto-scaling rules.
  • Soak and spike tests expose memory leaks, connection leaks and slow queues that short tests miss.
  • Performance budgets in CI block PRs that push p95 past the agreed threshold.
Think about it

Mental model

Imagine

A highway stress test

Think of

A load test against an API

Engineers close a new highway and push traffic from a trickle to bumper-to-bumper, measuring where speeds collapse and which exit backs up. Load tests do the same to an API: ramp concurrency, find the point where latency spikes, and trace which service or query is the jammed exit.

Mechanics

How it works

  • Define the scenario: endpoint mix, request rate, ramp profile, duration and the performance thresholds.
  • Generate load from a tool like k6 or Gatling, driving the endpoint with realistic request bodies and headers.
  • Instrument the run to capture latency percentiles, error rate, throughput and server-side metrics.
  • Compare the results against the SLO: for example p95 under 300ms and an error rate under 1% at the target RPS.
  • Analyze the bottleneck (app logs, DB stats, queues) and re-run to confirm the fix improved the numbers.

From scenario definition to a verified fix, with SLO gates along the way.

Scenario definition
Load generator
Endpoint under test
Metrics
Threshold check
Bottleneck hunt
Fix and re-run
Reference

Key commands

k6 run load.js

Run a k6 load script and print the summary metrics.

http_req_duration.........: avg=121.4ms  min=58.2ms  med=110.9ms  max=842.7ms  p(90)=210.5ms  p(95)=268.1ms
k6 run --summary-trend-stats="p(50),p(95),p(99)" load.js

Configure which percentile stats the summary prints.

k6 run -e BASE_URL=https://staging.example.com load.js

Pass an environment variable into the script for target switching.

k6 cloud run load.js

Run the test on managed infrastructure when local workers are not enough.

docker run --rm -v $(pwd):/scripts grafana/k6 run /scripts/load.js

Run the same script inside a container for reproducible runs in CI.

Hands-on

Examples

A k6 load test with ramp and thresholds

A k6 load test with ramp and thresholds
import http from "k6/http";
import { check, sleep } from "k6";
export const options = {
stages: [
{ duration: "30s", target: 50 },
{ duration: "1m", target: 200 },
{ duration: "30s", target: 0 },
],
thresholds: {
http_req_duration: ["p(95)<300"],
http_req_failed: ["rate<0.01"],
},
};
export default function () {
const res = http.get("https://api.example.com/v1/products?limit=10");
check(res, {
"status is 200": (r) => r.status === 200,
"response under 300ms": (r) => r.timings.duration < 300,
});
sleep(1);
}

stages ramp from 0 to 50 virtual users, then to 200, then back down — a realistic load profile instead of a flat line. thresholds fail the run if p95 latency passes 300ms or the error rate exceeds 1%. Each iteration performs a check on status and timing, and sleep(1) simulates a real user pausing between actions.

Real world

In production

  • Release gates compare the new build's p95 against the previous baseline before a deploy is approved.
  • Weekly load runs track latency trends so regressions appear as a bump in a dashboard, not an incident.
  • Capacity planning uses measured RPS ceilings to set instance counts and autoscaling thresholds.
  • Synthetic load during chaos experiments proves the API degrades gracefully when a dependency fails.
Guidelines

Best practices

  • Define thresholds before the test and treat crossing them as a failure, not an observation.
  • Use realistic data and payload sizes — synthetic tiny payloads understate real-world latency.
  • Run load tests against a staging environment with production-like data and capacity.
  • Report p50, p95 and p99, not averages, which hide tail latency.
  • Isolate the load test from other load so the numbers stay attributable and reproducible.
Watch out

Common mistakes

  • Using a single user looping fast, which measures raw latency but not real concurrency and contention.
  • Running against production and degrading real customer traffic during the test.
  • Only reporting the average response time, hiding the slow tail that users actually feel.
  • Ignoring the error rate, so a test that fails fast looks like great performance.
Root cause analysis

Common issues

Error
open connections exceeding database pool limit
Root cause

The pool max is smaller than the concurrency the load test is driving.

Fix

Raise the pool limit or reduce virtual users; check the app for leaked connections.

Error
p95 latency jumps sharply at a specific VU count
Root cause

A resource (database, thread pool, queue) saturates at that concurrency.

Fix

Profile server-side metrics at that stage, find the saturated resource, and scale or optimize it.

Error
k6 reports a connection-reset storm or huge failure rate
Root cause

The server or an intermediary (WAF, load balancer) refuses new connections under load.

Fix

Check connection and rate limits on the WAF or load balancer and raise them for the test.

When things break

Debugging tips

  • Check p99 and the error rate first — a low average with a long tail points to queueing or GC pauses.
  • Watch the server's connection pool and DB slow-query logs to find where latency actually accrues.
  • Compare results at each load stage to see whether latency degrades linearly or hits a cliff.
  • Use distributed tracing to break down time spent in each service during a slow request.
Interviews

Interview checkpoint

Practice

Hands-on lab

Baseline a Staging API with k6
45 min · advanced
In progress
Objectives
  • Write a k6 script with stages and thresholds
  • Run a short ramp against a staging API
  • Read the summary trend and percentile metrics
  • Set failure thresholds and gate a build on them

Baseline a Staging API with k6

45 min · advanced

0/5 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/3
1

Which metric best captures the tail latency that users actually experience?

2

A load test can pass with a perfect average even if a fraction of requests time out.

3

A soak test shows latency slowly climbing for an hour at constant load. What is the most likely cause?

Quick reference

Cheat sheet

k6 commands
k6 run load.jsRun a load script
k6 run --summary-trend-stats="p(50),p(95),p(99)" load.jsCustom percentile report
k6 run -e BASE_URL=<url> load.jsInject an env var
k6 cloud run load.jsScale out on managed workers
Metrics to watch
p95 / p99 latencyThe tail users feel
error rateShare of failed requests under load
RPS / throughputHow much work the API sustains
saturationCPU, connections, queue depth
Go deeper

Official documentation