{}DSA Atlas

Debugging & Edge Cases

A checklist of the inputs that break solutions and the bugs that appear again and again — use it before every submission.

Test inputs to always try

CategoryInputs
Empty[], "", None root, 0 nodes, 0 edges
Singleone element, one character, single node tree, 1×1 grid
Twotwo elements (many off-by-one bugs appear only here)
Duplicatesall equal, many repeated values
Sortedascending, descending (worst case for naive quicksort / BSTs)
Negatives & zeronegative numbers, zeros (division, products, sliding windows)
ExtremesINT_MIN, INT_MAX, max n (performance), max values (overflow)
Shapeskewed tree (linked-list-like), disconnected graph, self-loops, parallel edges, cycles
Stringsspaces, uppercase, non-letters, unicode (if relevant), palindromes of even and odd length
Answer boundariesanswer at index 0, at the last index, no answer at all

The most common bugs

Off-by-one

  • Loop bounds: range(n) vs range(n + 1), i < n vs i <= n.
  • Inclusive vs exclusive ranges — pick one convention per function and write it in a comment.
  • Prefix arrays of length n + 1 and the P[r+1] − P[l] formula.
  • Binary search termination (lo < hi vs lo <= hi) and updates (mid vs mid ± 1).

State that leaks

  • Global / class variables not reset between test cases (LeetCode reuses the Solution instance in some languages).
  • @cache defined at module level in Python keeps results across tests.
  • Backtracking without undoing the choice.
  • Appending a reference to a list that keeps changing (res.append(path) instead of path[:]).

Overflow and precision

  • Sums of 10⁵ numbers up to 10⁹ need 64 bits.
  • mid = (lo + hi) / 2 overflows in C++/Java; use lo + (hi − lo) / 2.
  • Products in modular arithmetic: take % MOD after every multiplication, in 64-bit.
  • Floating point comparisons — prefer integer arithmetic (cross products, squared distances, fractions via gcd).

Mutation surprises

  • Modifying a collection while iterating over it.
  • Sorting an input the caller still needs in its original order.
  • Marking grid cells as visited in the input and forgetting to restore them.

Graph-specific

  • Visited marked on dequeue instead of enqueue (BFS duplicates).
  • Missing reverse edges in undirected graphs.
  • Assuming the graph is connected.
  • Dijkstra with negative weights.

A debugging routine

  1. Reproduce with the smallest failing input you can construct.
  2. Print the key state at each step (window bounds, stack contents, dp row).
  3. Compare against a brute-force solution on random small inputs — the fastest way to find subtle bugs:
Python
import random

def brute(nums): ...
def fast(nums): ...

for _ in range(1000):
    nums = [random.randint(-5, 5) for _ in range(random.randint(0, 8))]
    if brute(nums) != fast(nums):
        print("MISMATCH", nums, brute(nums), fast(nums))
        break
  1. Check invariants: what should be true at the top of each loop iteration? Assert it.
  2. Re-read the problem statement — a surprising number of bugs are misread requirements.

Time limit exceeded?

  • Re-derive the complexity with real numbers: is n × m × k under ~10⁸?
  • Hidden O(n) operations inside loops: x in list, list.pop(0), slicing, string concatenation, s.substr, sorting.
  • Recursion without memoization revisiting the same states.
  • Python: move work out of inner loops, use local variables, prefer built-ins (sum, min, comprehensions).

Wrong answer on a huge test?

  • Overflow (C++/Java).
  • Recursion depth crash disguised as a wrong answer or runtime error.
  • An O(n²) fallback path taken only on specific shapes (sorted input, all duplicates).