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:

  1. Generator: produces random valid inputs

  2. Brute-force solution: simple, obviously-correct, slow (O(n²) or worse is fine)

  3. 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.

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:

  1. Find the smallest input that produces wrong output (stress tester helps here).

  2. Add a single checkpoint: print the state halfway through the algorithm.

  3. Is the state correct at the halfway point? If yes, bug is in the second half. If no, bug is in the first half.

  4. 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.