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.'
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.
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.
Mental model
A highway stress test
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.
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.
Key commands
k6 run load.jsRun 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.jsConfigure which percentile stats the summary prints.
k6 run -e BASE_URL=https://staging.example.com load.jsPass an environment variable into the script for target switching.
k6 cloud run load.jsRun the test on managed infrastructure when local workers are not enough.
docker run --rm -v $(pwd):/scripts grafana/k6 run /scripts/load.jsRun the same script inside a container for reproducible runs in CI.
Examples
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.
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.
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.
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.
Common issues
open connections exceeding database pool limitThe pool max is smaller than the concurrency the load test is driving.
Raise the pool limit or reduce virtual users; check the app for leaked connections.
p95 latency jumps sharply at a specific VU countA resource (database, thread pool, queue) saturates at that concurrency.
Profile server-side metrics at that stage, find the saturated resource, and scale or optimize it.
k6 reports a connection-reset storm or huge failure rateThe server or an intermediary (WAF, load balancer) refuses new connections under load.
Check connection and rate limits on the WAF or load balancer and raise them for the test.
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.
Interview checkpoint
Hands-on lab
- 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
Quick quiz
Which metric best captures the tail latency that users actually experience?
A load test can pass with a perfect average even if a fraction of requests time out.
A soak test shows latency slowly climbing for an hour at constant load. What is the most likely cause?
Cheat sheet
k6 run load.jsRun a load scriptk6 run --summary-trend-stats="p(50),p(95),p(99)" load.jsCustom percentile reportk6 run -e BASE_URL=<url> load.jsInject an env vark6 cloud run load.jsScale out on managed workersp95 / p99 latencyThe tail users feelerror rateShare of failed requests under loadRPS / throughputHow much work the API sustainssaturationCPU, connections, queue depth