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
| Category | Inputs |
|---|---|
| Empty | [], "", None root, 0 nodes, 0 edges |
| Single | one element, one character, single node tree, 1×1 grid |
| Two | two elements (many off-by-one bugs appear only here) |
| Duplicates | all equal, many repeated values |
| Sorted | ascending, descending (worst case for naive quicksort / BSTs) |
| Negatives & zero | negative numbers, zeros (division, products, sliding windows) |
| Extremes | INT_MIN, INT_MAX, max n (performance), max values (overflow) |
| Shape | skewed tree (linked-list-like), disconnected graph, self-loops, parallel edges, cycles |
| Strings | spaces, uppercase, non-letters, unicode (if relevant), palindromes of even and odd length |
| Answer boundaries | answer at index 0, at the last index, no answer at all |
The most common bugs
Off-by-one
- Loop bounds:
range(n)vsrange(n + 1),i < nvsi <= 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 < hivslo <= hi) and updates (midvsmid ± 1).
State that leaks
- Global / class variables not reset between test cases (LeetCode reuses the
Solutioninstance in some languages). @cachedefined 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 ofpath[:]).
Overflow and precision
- Sums of 10⁵ numbers up to 10⁹ need 64 bits.
mid = (lo + hi) / 2overflows in C++/Java; uselo + (hi − lo) / 2.- Products in modular arithmetic: take
% MODafter 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
- Reproduce with the smallest failing input you can construct.
- Print the key state at each step (window bounds, stack contents, dp row).
- 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
- Check invariants: what should be true at the top of each loop iteration? Assert it.
- 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).