04 — Stress Testing and Debugging¶
The most expensive moment in a rated contest is submitting a solution that passes your manual tests but fails on judge input. A penalty, a rank drop, and five minutes of panic — all avoidable. Stress testing is the practice of running your optimized solution against a known-correct brute-force on randomly generated inputs until you’ve either confirmed they agree or found the case where they diverge. This file shows you how to build and use that system.
What Stress Testing Is¶
Stress testing is a 3-component system:
Generator: produces random valid inputs
Brute-force solution: simple, obviously-correct, slow (O(n²) or worse is fine)
Optimized solution: your actual submission
You run them both on the same generated input, compare outputs, and repeat hundreds of times. When they disagree, you’ve found a bug. When they agree for 500+ cases, you have high confidence your optimized solution is correct.
This is not a production testing philosophy — it’s a targeted bug-finder for competitive programming where you already have the correct logic in a slow form and you’re checking if your fast version agrees.
How to Write a Stress Tester¶
The structure is always the same. Language doesn’t matter — pick whatever you’re fastest in.
Component 1: The Generator
// gen.cpp — generates random input
#include <bits/stdc++.h>
using namespace std;
int main(int argc, char* argv[]) {
mt19937 rng(atoi(argv[1])); // seed from command line for reproducibility
int n = rng() % 10 + 2; // small n for stress testing — bugs surface faster
cout << n << "\n";
for (int i = 0; i < n; i++) {
cout << (int)(rng() % 20) - 10; // values in [-10, 10]
if (i < n - 1) cout << " ";
}
cout << "\n";
return 0;
}
Component 2: Brute Force
// brute.cpp — your slow, obviously-correct solution
// Keep this simple. Correctness > speed here.
Component 3: Optimized Solution
// fast.cpp — your actual solution being verified
The Runner Script (bash)
#!/bin/bash
# stress.sh
g++ -O2 -o gen gen.cpp
g++ -O2 -o brute brute.cpp
g++ -O2 -o fast fast.cpp
for i in $(seq 1 500); do
./gen $i > input.txt
./brute < input.txt > out_brute.txt
./fast < input.txt > out_fast.txt
if ! diff -q out_brute.txt out_fast.txt > /dev/null; then
echo "MISMATCH on seed $i"
echo "Input:"
cat input.txt
echo "Brute output:"
cat out_brute.txt
echo "Fast output:"
cat out_fast.txt
break
fi
done
echo "All tests passed."
When this script finds a mismatch, you have a minimal reproducing case. Debug from there — not from a 10⁵ element array.
Key insight: keep n small in your generator during stress testing (n ≤ 15 or so). Bugs in logic surface on small inputs just as reliably as large ones, and small inputs run fast enough to execute 500+ iterations in seconds.
Common Bugs in Competitive Programming¶
These are the bugs that kill otherwise-correct solutions. Know them by name.
1. Integer Overflow¶
The most common kill in C++. int holds up to ~2 × 10⁹. If you multiply two int values that are each around 10⁵, the product (~10¹⁰) overflows silently.
// WRONG
int a = 100000, b = 100000;
int result = a * b; // overflows — undefined behavior
// CORRECT
long long result = (long long)a * b;
Rule: whenever you multiply two values that could individually reach 10⁵ or more, cast to long long before the multiplication. Not after.
2. Off-by-One in Binary Search¶
Binary search is famous for this. The invariant must hold at every iteration. When in doubt, use the template from 05_template_library.md and stop writing binary search from scratch.
// Classic off-by-one trap — mid+1 vs mid, lo vs hi vs lo+1
// Use your tested template. Don't improvise.
3. Uninitialized Variables / Arrays¶
In C++, local variables and VLAs are not zero-initialized. This is a silent, non-deterministic bug.
// WRONG — dp array contains garbage
int dp[1005][1005];
// CORRECT
int dp[1005][1005];
memset(dp, 0, sizeof(dp));
// or: vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
4. Wrong Modular Arithmetic (Negative Mods)¶
In C++, (-7) % 5 returns -2, not 3. If you’re computing modular arithmetic on values that could be negative, always add MOD before taking the mod:
// WRONG — can produce negative result
int result = (a - b) % MOD;
// CORRECT
int result = ((a - b) % MOD + MOD) % MOD;
5. Graph: Forgetting to Reset State Between Test Cases¶
If a problem has multiple test cases and you’re using a global visited array or adjacency list, forgetting to reset between cases produces wrong answers that are nearly impossible to debug without this mental model.
// In multi-test-case problems:
// Either re-initialize all state at the start of each test case,
// or use local variables inside the test case handler.
adj.clear(); // don't forget
fill(visited.begin(), visited.end(), false);
Debugging Strategy: Binary Search the Bug¶
When you have a wrong answer and you don’t know why, the instinct is to add print statements everywhere. This is slow and clutters your code before submission. The correct strategy is binary search the bug:
Find the smallest input that produces wrong output (stress tester helps here).
Add a single checkpoint: print the state halfway through the algorithm.
Is the state correct at the halfway point? If yes, bug is in the second half. If no, bug is in the first half.
Repeat — halve the search space each time.
Two or three checkpoints usually pinpoint the bug. Ten print statements create noise.
Edge Cases to Always Check¶
Before submitting any solution in a contest, run it mentally through this checklist:
Edge Case |
What Can Break |
|---|---|
Empty input (n = 0) |
Index out of bounds, incorrect base case |
Single element (n = 1) |
Off-by-one, base case DP |
All same elements |
Hash collision, binary search, deduplication logic |
Maximum constraints (n = 10⁵, values = 10⁹) |
Overflow, TLE, MLE |
Minimum constraints (n = 1, values = -10⁹) |
Sign errors, empty loop |
Negative numbers |
Modular arithmetic, absolute value, min/max confusion |
Already sorted input |
“Optimized” sorts or algorithms that assume unsorted |
Reverse-sorted input |
Descending order edge in greedy |
Graph with no edges |
Disconnected graph, isolated vertices |
Graph with self-loops |
If problem doesn’t mention them, they might appear |
You won’t have time to test all of these in every contest. But the first three (empty, single element, all same) catch a disproportionate number of bugs. Always run those.
What Most People Get Wrong¶
Most competitors submit their optimized solution without stress testing it — especially when the brute force is “obvious” and the optimized solution “feels right.” This is how a subtle off-by-one in a binary search or a missing +MOD costs you 50 minutes of penalty.
The second mistake: writing a stress tester that generates inputs too large for the brute force. If your brute force is O(n²) and you’re generating n = 10⁵, it’ll never finish. Keep n small (≤ 20 for most problems) during stress testing. The bugs you care about don’t hide at large n — they hide in edge cases and logic errors that appear at n = 5.
The third mistake: running stress testing only on the final solution instead of after each major implementation step. If you stress test after completing the full algorithm and find a bug, you have to debug the entire thing. Stress test incrementally when possible.