Q6 of 38 · Performance
Walk through designing a load test for a checkout flow.
Short answer
Short answer: Map the user journey end-to-end, parametrise with realistic data (carts, addresses, cards), mix transaction types by production ratio, model think times, ramp gradually, hold at target, and assert SLOs at the checkout-completed step rather than per individual call.
Detail
Step 1 — Map the journey. Login → browse → add to cart → view cart → enter shipping → enter payment → confirm. Capture each HTTP call (or business transaction) and the data dependencies between them — order ID from "create cart" feeds "add line item," etc.
Step 2 — Parametrise data. A real checkout test cannot use the same user, cart, or card for every iteration — the system de-duplicates, sessions collide, payment provider rate-limits, idempotency keys clash. Generate per-iteration data: unique user, unique cart, varied product mix, different shipping zones, randomised payment methods.
Step 3 — Mix transaction types by production ratio. Production has more browsers than buyers — maybe 100 product-page views per checkout. A pure checkout-loop test under-loads the read paths. Use weighted scenarios: 70% browse-only, 25% browse-then-cart-abandon, 5% complete checkout.
Step 4 — Model think times. Real users pause to read product details, type addresses, decide on payment. Random pauses (1-5s between steps) keep server-side concurrency realistic. Without them, you measure system limits, not user experience.
Step 5 — Ramp gradually. Spike-from-zero to peak load skips the warm-up phase real systems get during morning ramp. 5-10 minute ramp to peak, hold for 30+ minutes, then ramp down.
Step 6 — Assert at the right level. Per-call latency thresholds are useful but the user-facing SLO is "checkout completes in under 3 seconds." Track the end-to-end transaction duration explicitly; per-call assertions catch infrastructure issues, the end-to-end one catches user-impacting regressions.
// EXAMPLE
k6-checkout-flow.js
import http from 'k6/http';
import { check, group, sleep } from 'k6';
import { SharedArray } from 'k6/data';
const users = new SharedArray('users', () =>
JSON.parse(open('./users.json'))
);
export const options = {
scenarios: {
browse: { executor: 'ramping-vus', exec: 'browseOnly',
startVUs: 0, stages: [{ duration: '5m', target: 70 }] },
checkout: { executor: 'ramping-vus', exec: 'checkout',
startVUs: 0, stages: [{ duration: '5m', target: 5 }] },
},
thresholds: {
'group_duration{group:::checkout}': ['p(95)<3000'],
http_req_failed: ['rate<0.005'],
},
};
export function checkout() {
const u = users[__VU % users.length];
group('checkout', () => {
const cart = http.post('/cart', { sku: 'A1' }).json();
sleep(2);
http.post('/cart/' + cart.id + '/address', u.address);
sleep(3);
http.post('/cart/' + cart.id + '/pay', u.card);
});
}// WHAT INTERVIEWERS LOOK FOR
// COMMON PITFALL
// Related questions
What is the difference between load testing and stress testing?
Performance
What's the difference between load testing and stress testing in how you actually execute them?
Performance
How would you compare JMeter, k6, and Gatling at a high level?
Performance
How does ExecutorService work? Why use it over creating threads directly?
Core Java