๐Ÿ“– Appendix F โฑ๏ธ ~30 min read ๐ŸŽฏ All Levels

Appendix F: Debugging Guide โ€” Common Bugs & How to Fix Them

๐Ÿ’ก Why This Appendix? Even correct algorithmic thinking fails when bugs slip through. This guide is a systematic catalogue of the most common bugs in competitive programming C++ code, organized by category. Bookmark it and check here first when your solution gives WA (Wrong Answer), TLE (Time Limit Exceeded), RE (Runtime Error), or MLE (Memory Limit Exceeded).


F.1 Integer Overflow

The most common source of Wrong Answer in C++.

Problem: int is Too Small

int holds values up to ~2.1 ร— 10โน (โ‰ˆ 2 ร— 10โน). Many problems exceed this.

// โŒ WRONG: n*n can overflow when n = 10^5
int n = 100000;
int result = n * n;  // = 10^10 โ†’ overflows int (max ~2ร—10^9)!

// โœ… CORRECT: cast to long long before multiplication
long long result = (long long)n * n;  // = 10^10, fits in long long
// OR:
long long n_ll = n;
long long result2 = n_ll * n_ll;

When to Use long long

SituationUse long long?
Array values up to 10โน, need range sumsโœ… Yes (sum can be 10โน ร— 10โต = 10ยนโด)
Prefix sums of up to 10โต elementsโœ… Yes (safe default)
Matrix entries, intermediate DP valuesโœ… Yes
Distances in shortest path (Dijkstra)โœ… Yes (dist[u] + w can overflow int)
Simple counters (0 to N where N โ‰ค 10โถ)โŒ int is fine
Indices and loop variablesโŒ int is fine

Dangerous Operations

// โŒ Overflow examples:
int a = 1e9, b = 1e9;
cout << a + b;     // overflow (answer > INT_MAX)
cout << a * 2;     // overflow
cout << a * a;     // catastrophic overflow

// โŒ Comparison overflow:
if (a * b > 1e18) ...  // a*b itself may have overflowed!

// โœ… Safe versions:
cout << (long long)a + b;
cout << (long long)a * 2;
cout << (long long)a * a;
if ((long long)a * b > (long long)1e18) ...  // compare as long long

INF Value Choice

// โŒ WRONG: Using INT_MAX as infinity in Dijkstra
const int INF = INT_MAX;
if (dist[u] + w < dist[v]) ...  // dist[u] + w OVERFLOWS if dist[u]=INT_MAX!

// โœ… CORRECT: Use a safe sentinel
const long long INF = 1e18;   // for long long distances
const int INF_INT = 1e9;       // for int distances (leave headroom for addition)

F.2 Off-By-One Errors

The second most common source of WA.

Array Indexing

// โŒ WRONG: Array out of bounds (accessing index n)
int A[n];
for (int i = 0; i <= n; i++) cout << A[i];  // A[n] is undefined!

// โœ… CORRECT
for (int i = 0; i < n; i++) cout << A[i];   // indices 0..n-1
// OR (1-indexed):
for (int i = 1; i <= n; i++) cout << A[i];  // indices 1..n

Prefix Sum Formula

// โŒ WRONG: Off-by-one in range sum
// sum(L, R) should be P[R] - P[L-1], NOT P[R] - P[L]
cout << P[R] - P[L];    // missing element A[L]!

// โœ… CORRECT
cout << P[R] - P[L-1];  // P[0]=0 handles the L=1 case correctly

Binary Search Boundaries

// Finding first index where A[i] >= target (lower_bound behavior):

// โŒ WRONG: Common binary search mistakes
int lo = 0, hi = n - 1;
while (lo < hi) {
    int mid = (lo + hi) / 2;
    if (A[mid] < target) lo = mid;      // BUG: should be lo = mid + 1
    else hi = mid - 1;                   // BUG: should be hi = mid
}

// โœ… CORRECT: Standard lower_bound template
int lo = 0, hi = n;  // hi = n (not n-1!) to allow "not found" answer
while (lo < hi) {
    int mid = (lo + hi) / 2;
    if (A[mid] < target) lo = mid + 1;  // target is in [mid+1, hi]
    else hi = mid;                       // target is in [lo, mid]
}
// lo = hi = first index with A[i] >= target; lo=n means not found

Loop Bounds

// โŒ Common mistake: loop runs one too few or many times
for (int i = 1; i < n; i++) ...    // misses i=n if you meant i=0 to n-1
for (int i = 0; i <= n-1; i++) ... // OK but confusing; prefer i < n

// DP table filling: check if the recurrence accesses i-1
// โŒ If dp[i] uses dp[i-1], and i starts at 0, then dp[-1] is undefined!
for (int i = 0; i <= n; i++) {
    dp[i] = dp[i-1] + ...;  // BUG when i=0: dp[-1]!
}

// โœ… Start at i=1, or initialize dp[0] as base case separately
dp[0] = BASE_CASE;
for (int i = 1; i <= n; i++) {
    dp[i] = dp[i-1] + ...;  // safe: dp[i-1] always valid
}

F.3 Uninitialized Variables

// โŒ WRONG: dp array not initialized
int dp[1005][1005];  // contains garbage values in C++!
// dp[i][j] might be non-zero from previous test cases or OS memory

// โœ… CORRECT options:
// Option 1: memset (fills bytes, use 0 or 0x3f for near-infinity)
memset(dp, 0, sizeof(dp));          // fills with 0
memset(dp, 0x3f, sizeof(dp));       // fills with ~1.06e9 (useful as "infinity" for int)

// Option 2: vector with explicit initialization
vector<vector<int>> dp(n+1, vector<int>(m+1, 0));

// Option 3: fill
fill(dp, dp + n, 0);

// โš ๏ธ WARNING: memset(dp, -1, sizeof(dp)) fills each BYTE with 0xFF
// For int: 0xFFFFFFFF = -1 (works for "unvisited" marker)
// For long long: 0xFFFFFFFFFFFFFFFF = -1 (also works)
// But memset(dp, 1, sizeof(dp)) gives 0x01010101 = 16843009, not 1!

Global vs Local Arrays

// Global arrays are zero-initialized by default in C++
// Local (stack) arrays are NOT initialized

int globalArr[100005];     // โœ… initialized to 0
int globalDP[1005][1005];  // โœ… initialized to 0

int main() {
    int localArr[1000];    // โŒ NOT initialized (garbage values)
    int localDP[100][100]; // โŒ NOT initialized
    
    // Tip: Declare large arrays globally to avoid stack overflow AND ensure init
}

F.4 Stack Overflow (Recursion Too Deep)

// C++ default stack size is typically 1-8 MB
// Deep recursion can exceed this โ†’ Runtime Error (segfault)

// โŒ Dangerous: DFS/recursion on tree of depth 10^5
void dfs(int u) { dfs(children[u]); }  // stack overflow for long chains!

// โœ… FIX 1: Convert to iterative using explicit stack
void dfs_iterative(int start) {
    stack<int> st;
    st.push(start);
    while (!st.empty()) {
        int u = st.top(); st.pop();
        for (int v : children[u]) st.push(v);
    }
}

// โœ… FIX 2: Increase stack size (platform-specific, contest judges often allow this)
// On Linux, compile and run with: ulimit -s unlimited && ./sol

// Rule of thumb:
// Recursion depth up to ~10^4: usually safe
// Recursion depth up to ~10^5: risky, consider iterative
// Recursion depth up to ~10^6: almost certainly stack overflow โ†’ use iterative

F.5 Modular Arithmetic Bugs

// When the problem asks for answer mod 10^9+7:
const int MOD = 1e9 + 7;

// โŒ WRONG: Forgot to mod, result overflows long long
long long dp = 1;
for (int i = 0; i < n; i++) dp *= A[i];  // overflows after ~18 large multiplications!

// โŒ WRONG: Subtraction underflow (result is negative mod)
long long ans = (a - b) % MOD;  // if a < b, result is negative in C++!

// โœ… CORRECT: Add MOD before taking mod of a subtraction
long long ans = ((a - b) % MOD + MOD) % MOD;  // guaranteed non-negative

// โŒ WRONG: Forgetting to mod intermediate values in DP
dp[i][j] = dp[i-1][j] + dp[i][j-1];  // can overflow if iterations are many

// โœ… CORRECT: Mod every addition
dp[i][j] = (dp[i-1][j] + dp[i][j-1]) % MOD;

// โœ… CORRECT modular exponentiation:
long long modpow(long long base, long long exp, long long mod) {
    long long result = 1;
    base %= mod;
    while (exp > 0) {
        if (exp & 1) result = result * base % mod;  // โ† mod after each multiply!
        base = base * base % mod;
        exp >>= 1;
    }
    return result;
}

F.6 Graph / BFS / DFS Bugs

// โŒ BFS: Forgetting to mark visited BEFORE entering queue
// This causes nodes to be processed multiple times!
queue<int> q;
q.push(src);
while (!q.empty()) {
    int u = q.front(); q.pop();
    visited[u] = true;  // โŒ Marking AFTER dequeue โ†’ same node pushed multiple times
    for (int v : adj[u]) if (!visited[v]) q.push(v);
}

// โœ… CORRECT: Mark visited when ADDING to queue
visited[src] = true;
queue<int> q;
q.push(src);
while (!q.empty()) {
    int u = q.front(); q.pop();
    for (int v : adj[u]) {
        if (!visited[v]) {
            visited[v] = true;  // โœ… Mark BEFORE pushing
            q.push(v);
        }
    }
}

// โŒ DFS: Forgetting to reset visited between test cases
// In problems with multiple test cases, reinitialize visited[]!
memset(visited, false, sizeof(visited));

// โŒ Dijkstra: Using int instead of long long for distances
int dist[MAXN];  // โŒ if edge weights can be up to 10^9, sum overflows!
long long dist[MAXN];  // โœ…

F.7 I/O Bugs

// โŒ WRONG: Missing ios_base::sync_with_stdio(false) for large I/O
// Without this, cin/cout are synced with C stdio โ†’ very slow!
// For N = 10^6 inputs, this can be the difference between AC and TLE.

// โœ… ALWAYS add at start of main() for competitive programming:
ios_base::sync_with_stdio(false);
cin.tie(NULL);

// โŒ WRONG: Using endl (flushes buffer every line โ†’ slow)
for (int i = 0; i < n; i++) cout << ans[i] << endl;  // slow!

// โœ… CORRECT: Use "\n" instead
for (int i = 0; i < n; i++) cout << ans[i] << "\n";  // fast

// โŒ WRONG: Mixing cin and scanf/printf after disabling sync
ios_base::sync_with_stdio(false);
scanf("%d", &n);  // BUG: mixing C and C++ I/O after desync!

// โœ… CORRECT: Pick ONE and stick with it
// Either use cin/cout exclusively, or scanf/printf exclusively

// USACO file I/O (when required):
freopen("problem.in", "r", stdin);
freopen("problem.out", "w", stdout);
// After these lines, cin/cout work with files automatically

F.8 2D Array Bounds and Directions

// Grid BFS: off-by-one in boundary checking
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};

// โŒ WRONG: Bounds check is wrong (allows -1 index)
for (int d = 0; d < 4; d++) {
    int nx = x + dx[d], ny = y + dy[d];
    if (nx >= 0 && ny >= 0 && nx < n && ny < m) // โœ… This is actually correct!
    // Just make sure you check ALL FOUR conditions
}

// โŒ WRONG: Wrong dimensions (swapping rows and columns)
// If grid is N rows ร— M columns:
// A[row][col]: row goes 0..N-1, col goes 0..M-1
// Bounds: row < N, col < M  (NOT row < M!)

// โŒ WRONG: Visiting same cell multiple times (forgetting dist check)
// In multi-source BFS for distance:
if (!visited[nx][ny]) {  // โœ… Only visit unvisited cells
    visited[nx][ny] = true;
    dist[nx][ny] = dist[x][y] + 1;
    q.push({nx, ny});
}

F.9 DP-Specific Bugs

// โŒ WRONG: 0/1 Knapsack inner loop direction
// Must iterate capacity from HIGH to LOW to prevent reusing items!
for (int i = 0; i < n; i++) {
    for (int j = W; j >= weight[i]; j--) {  // โœ… HIGH to LOW
        dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    }
}
// If you iterate j from LOW to HIGH:
for (int j = weight[i]; j <= W; j++) {  // โŒ LOW to HIGH = unbounded knapsack!
    dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}

// โŒ WRONG: LIS with binary search โ€” using upper_bound vs lower_bound
// For STRICTLY increasing LIS: use lower_bound (find first >= x, replace)
// For NON-DECREASING LIS: use upper_bound (find first > x, replace)
auto it = lower_bound(tails.begin(), tails.end(), x);  // strictly increasing
auto it = upper_bound(tails.begin(), tails.end(), x);  // non-decreasing

// โŒ WRONG: Forgetting base cases
// dp[0] or dp[i][0] or dp[0][j] MUST be explicitly set before the main loop
dp[0][0] = 0;  // always initialize base cases!

F.10 Memory Limit Exceeded (MLE)

// Common causes of MLE:

// โŒ Array too large for the problem
int dp[10005][10005];  // = 10^8 ints = 400MB โ†’ exceeds typical 256MB limit!

// Calculate: N*M*sizeof(type) bytes
// int: 4 bytes, long long: 8 bytes
// 256MB = 256 ร— 10^6 bytes
// Max int array: 64 ร— 10^6 elements
// Max long long array: 32 ร— 10^6 elements

// โœ… Space optimization for 1D DP:
// If dp[i] only depends on dp[i-1], use rolling array:
vector<long long> dp(2, 0);  // dp[cur] and dp[prev]
for (int i = 0; i < n; i++) {
    dp[1 - cur] = f(dp[cur]);  // alternate between 0 and 1
    cur = 1 - cur;
}

// โœ… Space optimization for 2D DP (knapsack-style):
// If dp[i][j] only depends on dp[i-1][...], keep only two rows
vector<int> prev_row(W+1, 0), curr_row(W+1, 0);

Quick Diagnosis Checklist

When you get WA/RE/TLE, go through this checklist:

Wrong Answer (WA):

  • Integer overflow? (Add long long casts or change types)
  • Off-by-one in array bounds, loop bounds, range sum formula?
  • Uninitialized array? (Add memset or use vector with init)
  • Wrong DP transition direction? (0/1 knapsack: high-to-low)
  • Wrong binary search template? (Verify on [1,2,3] for target 2)
  • Edge cases: empty input, N=0, N=1, all equal elements?

Runtime Error (RE):

  • Array out of bounds? (Add bounds checks or use vector)
  • Stack overflow from deep recursion? (Convert to iterative)
  • Null/invalid pointer dereference?
  • Division by zero?

Time Limit Exceeded (TLE):

  • Missing ios_base::sync_with_stdio(false); cin.tie(NULL);?
  • O(Nยฒ) algorithm when N=10โต needs O(N log N)?
  • Unnecessary recomputation in DP? (Need memoization)
  • BFS visiting nodes multiple times? (Mark visited before pushing)

Memory Limit Exceeded (MLE):

  • 2D array too large? (Calculate Nร—Mร—sizeof bytes)
  • Recursive DFS with implicit call stack too deep?
  • Dynamic memory allocation in tight loop?

๐Ÿ’ก Pro Tip: Print your intermediate values! cerr << "DEBUG: dp[3] = " << dp[3] << "\n"; cerr goes to stderr (not stdout), so it won't affect your output in competitive programming judges. Remove all cerr lines before final submission.