Performance and Load Testing Questions
Evaluating a system under expected and extreme load. Covers load, stress, soak, and scalability testing, defining performance goals, generating load, and interpreting latency and throughput results. Includes identifying bottlenecks and validating capacity before release.
Design an approach to run consumer-driven contract tests that include performance characteristics between services, not just functional contracts. Explain how to capture expected latency and throughput, how to simulate load without causing dependency overload, and how to fail a CI pipeline for performance regressions while minimizing false positives.
Sample Answer
Approach summary: extend consumer-driven contract (CDC) tests to include performance SLIs (latency percentiles, throughput) by codifying expected performance in the contract, executing controlled load profiles driven by the consumer’s expected usage, and gating CI with statistically robust regression detection.
Design:
- Contract shape: add performance section to CDC (e.g., expected p50/p95/p99 latencies, RPS, concurrency, max error-rate). Store with consumer contract (PACT extensions or custom YAML).
- Baseline: capture stable baseline metrics from production or a representative staging environment under representative synthetic traffic. Store baselines with timestamps and tags (commit, artifact).
Simulation without overload:
- Use service virtualization / lightweight mocks for downstream dependencies that are expensive to hit (Hoverfly, WireMock, mock servers seeded from contract examples). For the provider under test, prefer a staging replica with rate limits/quotas.
- Generate load from an isolated load harness (k6, Gatling, k6 Cloud) that replays consumer scenarios derived from the contract. Use gradual ramp-up, warm-up, and steady-state windows to avoid spikes.
- Apply throttles and circuit-breakers on provider side in staging; use traffic shaping to mimic real traffic mix.
Execution and instrumentation:
- Collect request/response times, percentiles, error rates, CPU/mem, and business metrics via Prometheus/Grafana or the load tool’s exporters.
- Use percentiles (p50,p95,p99), throughput, and SLA pass/fail rules; prefer p95/p99 over mean.
CI gating and minimizing false positives:
- Don’t fail on single-run noise. Use:
- Warm-up period ignored
- Multiple runs (e.g., 3) and compare median/CI
- Statistical test (e.g., Mann–Whitney U or bootstrap) comparing current run to baseline with configurable alpha
- Minimum sample size and minimum RPS to ensure meaningful results
- Relative thresholds (e.g., regression >10% and absolute threshold exceeded)
- Add grace windows: only fail if regression persists across N consecutive runs or across a canary deployment.
- Tag tests: quick smoke (functional + light perf), full perf (nightly or pre-release) to avoid slowing every PR.
- Provide actionable failures: include diffs of percentiles, CPU/mem, and request traces to help debugging.
Operationalization:
- Extend CDC pipelines to run performance contracts in dedicated CI agents with resource isolation.
- Use artifact storage for baselines and trend graphs; integrate Slack/GitHub annotations.
- For high-risk changes, run canary traffic with real traffic percentages and automatic rollback on breach.
Trade-offs:
- Mocking avoids provider overload but can hide provider-side bottlenecks — complement with periodic real-provider tests.
- Strict CI gating increases developer friction; use staged enforcement (warnings → gates).
This approach balances realism and safety, codifies expectations in contracts, and uses statistical gating to reduce false positives while surfacing true regressions early.
What performance and benchmark tests would you design to ensure correctness under pathological inputs that trigger algorithmic worst-case behavior (e.g., degenerate graphs, many nested loops, long regexes)? Include how to define acceptance criteria and detect regressions in CI.
Sample Answer
Approach summary:
- Intentionally generate pathological inputs that force algorithmic worst-cases, run controlled benchmarks across input sizes, and assert both functional correctness and bounded resource/latency behavior. Track baselines and alert on regressions in CI.
Test types & how to build them:
- Degenerate graphs
- Generator: create long chains, star graphs, complete graphs, and adversarial topologies for the specific algorithm (e.g., Dijkstra with negative cycles, Bellman-Ford worst-case).
- Metrics: runtime, memory, number of relaxations/visits.
- Nested-loop blowups
- Generator: construct inputs that maximize inner-loop iterations (e.g., sorted/reversed arrays for naive n^2 sorts; many collisions for hash-table probing).
- Metrics: operations count, CPU, wall-clock per N.
- Long/complex regexes
- Generator: pathological regex patterns and long inputs that trigger catastrophic backtracking.
- Metrics: match time, stack depth, timeouts.
- Randomized stress + adversarial hybrid
- Combine fuzzing with property-based tests (Hypothesis/QuickCheck) to find corner cases and reproduce deterministic seeds.
- Resource exhaustion tests
- Large memory allocations, many file descriptors, deep recursion to test graceful failure and timeouts.
Measurement design:
- Sweep input size N in geometric steps (e.g., 1k, 2k, 4k, 8k) and fit observed runtime to expected complexity (O(n), O(n log n), O(n^2), …). Report R^2 and coefficient.
- Collect p50/p95/p99 latencies, CPU %, RSS, GC pauses, and operation counts/counters.
- Capture traces/profiles (flamegraphs, heap snapshots) for failures.
Acceptance criteria:
- Functional: output equals reference/oracle for pathological inputs.
- Performance: for each scenario, define acceptable growth (e.g., runtime <= C * N log N for algorithm X, p99 < 500ms for N=10k). Store numerical baselines from canonical runs and tolerances (e.g., +10% for p95, absolute caps for p99).
- Safety: no unbounded memory growth, no unhandled crashes, and tests must finish within overall timeout.
CI & regression detection:
- Separate pipeline stage: nightly/merge-bench jobs for heavy tests; lightweight unit stress tests on PRs.
- Baselines: persist benchmark artifacts (benchmarks DB or JSON) keyed by commit/tag. On each run compute ratio to baseline and statistical significance (e.g., using median of k runs; use t-test or non-parametric test).
- Fail criteria: exceed tolerance or statistically significant degradation beyond threshold -> block merge or raise ticket depending on severity.
- Automations: attach flamegraphs and full logs to CI failure for triage; open performance regressions issues automatically with links to artifacts and commit range (git-bisect-friendly).
- Reproducibility: deterministic seeds, fixed hardware/container limits, Docker images, and resource isolation; run multiple repeats to reduce noise.
Example quick checklist for a PR:
- Add unit correctness tests with pathological inputs (fast).
- Add micro-benchmark and record expected slope/limit.
- If micro-benchmark regresses >10% p95, CI warns; >30% blocks merge and creates issue with profiles.
This approach ensures correctness and bounded behavior under pathological inputs while making regressions visible, reproducible, and actionable in CI.
Design a unit test strategy for a core algorithm that has performance constraints (e.g., must complete within 100ms). How do you test both correctness and performance deterministically in CI?
Sample Answer
Strategy: separate correctness tests from performance benchmarks; make performance deterministic by controlling environment and inputs.
Correctness:
- Unit tests covering edge cases, invariants, and randomized property tests with fixed seeds.
- Use mocks/stubs for slow external dependencies to keep tests fast and deterministic.
Performance:
- Create microbenchmarks that run the core algorithm with representative inputs and warm-up cycles. Use a calibrated harness: fix CPU governor, isolate CI worker, disable background services.
- Measure median and p95 of N runs (e.g., 20 warm-ups + 50 measured runs) and assert median <100ms with small tolerance.
- Use statistical checks: compare baseline distributions and fail if regression >10% with p-value threshold.
CI integration:
- Run correctness suite on every PR; run fast performance benchmarks on merges to main using dedicated, provisioned runners identical to baseline. Store baselines in artifacts; run regression detector.
- If nondeterministic, mark test as flaky and require triage. Maintain microbench harness in repo and record environment metadata.
Determinism tips: fix random seeds, preallocate resources, and use mock timers for timeouts where possible.
Design a CI pipeline to detect performance regressions and numerical regressions for a core algorithm. Describe how to choose benchmarks and sample sizes, establish baselines, run tests in stable environments, apply statistical tests (t-test, bootstrap), and alert on meaningful regressions while avoiding noise. Include strategies for gating PRs and for long-term trend monitoring.
Sample Answer
Requirements & constraints:
- Detect meaningful performance (latency/throughput/memory) and numerical (algorithm output differences) regressions automatically.
- Low false positives; quick feedback for PRs; historical trend tracking.
- Reproducible, isolated runs; support deterministic and stochastic algorithms.
High-level architecture:
CI job → Controlled runner(s) in stable infra → Benchmark harness → Metrics DB / time-series store → Stats engine → Alerting + PR gating UI + dashboards.
Choosing benchmarks & sample sizes:
- Pick representative workloads: microbenchmarks (hot inner loops), integration benchmarks (real input sizes), and end-to-end scenarios.
- For numerical checks, include corner-case inputs and randomized seeds saved for reproducibility.
- Determine sample size via pilot runs: measure variance σ²; choose n so standard error SE = σ/√n gives detectable delta d with desired power (80–90%) and α (0.01–0.05). Use power analysis: n ≈ ( (Z1-α/2 + Zpower) * σ / d )².
- For low-latency functions, use >30 samples; for high variance workloads, increase n or use larger workloads to reduce relative noise.
Baselines & stable environments:
- Establish baseline as rolling median or percentile over a stable window (e.g., last 7 successful builds or 30 days excluding known anomalies).
- Run benchmarks on pinned hardware images / containers, isolated CPU cores, disabled turbo-scaling, consistent OS kernel, and controlled background load. Record hardware/driver versions.
Statistical testing & thresholds:
- Use parametric tests (Welch’s t-test) when normality holds; otherwise use bootstrap or permutation tests for robust p-values and confidence intervals.
- For performance: compute effect size (relative % change) and confidence interval. Require both statistical significance (p < α) and practical significance (change > threshold, e.g., 2–5%) to alert.
- For numerical regressions: compare outputs with tolerance: absolute/relative thresholds, plus statistical tests over distributions. Use bit-level checks for deterministic failures.
- Use multiple-test correction (Benjamini-Hochberg) when running many benchmarks to control false discovery rate.
Avoiding noise & flakiness handling:
- Require regression to persist across k consecutive runs or exceed aggregated significance across multiple builds before escalating.
- Maintain a "flaky" tag for benchmarks with high variance; increase sample size or move to nightly.
- Normalize for environment covariates (CPU frequency, temperature) and log metadata to filter out systemic outliers.
Gating PRs:
- Fast path: lightweight smoke benchmarks with small n and higher thresholds for immediate feedback.
- Full check: run comprehensive benchmarks in a pre-merge pipeline or as required by policy. Block merges only if both statistical and practical thresholds are exceeded or manual approval provided.
- Provide actionable diffs in PRs: which metric regressed, magnitude, CI, links to historical trend and reproducible input.
Long-term trend monitoring:
- Store raw sample data and metadata in a time-series DB. Build dashboards with control charts (e.g., EWMA, CUSUM) to detect slow drifts.
- Run periodic baseline recalibration and retrain thresholds based on seasonality/hardware changes.
- Automate periodic reruns and anomaly detection (isolation forest or change-point detection) to surface regressions not tied to a single PR.
Operational considerations:
- Cost-control: run full suites on scheduled nightly or on-demand; keep quick checks for PRs.
- Auditability: retain raw runs, seeds, and environment snapshots to reproduce.
- Communication: tiered alerts (Slack/email/issue) with severity and reproduction steps; allow engineers to mark expected regressions (feature flags).
This design balances sensitivity and robustness by combining careful sampling, appropriate statistical tests, environmental control, and staged gating to minimize noise while catching meaningful regressions.
That is every published Performance and Load Testing question for Software Engineer so far. Browse the other topics in this category, or practice this one interactively.