Input Parsing, Testing, and Debugging for Challenges
Getting the algorithm right is only half the battle in a coding challenge. You also have to read the input correctly, catch your own bugs before the judge does, and remember the edge cases that trip up most submissions. This page covers the practical workflow: parse, test, debug, and check the edges. Use it alongside the algorithms themselves.
New to the track? Read Python for DSA first for the language basics.
Reading input#
Most challenge sites feed input on standard input. The core tools are input() for one line at a time and sys.stdin for reading everything at once.
1# One line, then split into integers
2line = input() # e.g. "3 1 4 1 5"
3nums = [int(x) for x in line.split()]
4print(nums) # [3, 1, 4, 1, 5]
5
6# A single integer
7n = int(input())
8
9# First line is a count, then n lines follow
10count = int(input())
11values = [int(input()) for _ in range(count)]
For large inputs, reading everything at once is faster than calling input() in a loop:
1import sys
2
3data = sys.stdin.read().split() # all whitespace-separated tokens
4# Walk the tokens with an index
5it = iter(data)
6n = int(next(it))
7nums = [int(next(it)) for _ in range(n)]
Always .strip() a line before parsing when trailing spaces or newlines might sneak in, and remember split() with no argument handles any run of whitespace.
Quick tests with assert#
You do not need a test framework to check your work. A few assert statements catch most mistakes and run instantly. An assert raises AssertionError when its condition is false and does nothing when it is true.
1def two_sum(nums, target):
2 seen = {}
3 for i, n in enumerate(nums):
4 if target - n in seen:
5 return [seen[target - n], i]
6 seen[n] = i
7 return []
8
9# Each assert documents expected behavior
10assert two_sum([2, 7, 11, 15], 9) == [0, 1]
11assert two_sum([3, 2, 4], 6) == [1, 2]
12assert two_sum([1, 2, 3], 100) == [] # no pair found
13print("all tests passed")
Keep the asserts next to the function while you develop, then remove or comment them before final submission if the judge only wants the answer. A failing assert points you straight at the case that broke.
Table-driven tests#
When you have many cases, a list of (input, expected) tuples keeps them tidy and easy to extend.
1def is_palindrome(s):
2 cleaned = [c.lower() for c in s if c.isalnum()]
3 return cleaned == cleaned[::-1]
4
5cases = [
6 ("racecar", True),
7 ("A man a plan a canal Panama", True),
8 ("hello", False),
9 ("", True), # empty string is a palindrome
10]
11
12for text, expected in cases:
13 result = is_palindrome(text)
14 assert result == expected, f"failed on {text!r}: got {result}"
15print("all cases passed")
The !r in the f-string prints the repr, which shows quotes and makes an empty or whitespace input obvious in the failure message.
Print debugging#
The fastest way to understand wrong output is to print the state at key points. Label each print so you know what you are looking at, and print inside loops to watch values change.
1def running_max(nums):
2 best = nums[0]
3 for i, n in enumerate(nums):
4 best = max(best, n)
5 print(f"i={i} n={n} best={best}") # trace the loop
6 return best
7
8running_max([3, 1, 4, 1, 5])
Print the type when a bug might be a string-versus-int mix-up: print(type(x), x). When output clutters the screen, print to standard error so it does not mix with the answer: import sys; print("debug", file=sys.stderr).
Stepping through with pdb#
For a bug you cannot see from prints, the built-in debugger pdb lets you pause and inspect. Drop breakpoint() where you want to stop, then run the program.
1def buggy_sum(nums):
2 total = 0
3 for n in nums:
4 breakpoint() # execution pauses here
5 total += n
6 return total
7
8# In the pdb prompt:
9# n -> next line
10# c -> continue to the next breakpoint
11# p total -> print a variable
12# l -> list surrounding code
13# q -> quit the debugger
The most useful commands are n (step over), c (continue), p name (print a value), and q (quit). breakpoint() is the modern entry point and works the same as import pdb; pdb.set_trace().
Edge cases to always check#
Most failed submissions break on the boundaries, not the typical case. Run through this checklist before you submit:
1# Empty input
2assert sum([]) == 0
3
4# Single element
5assert max([7]) == 7
6
7# All-equal or duplicate values
8assert len(set([2, 2, 2])) == 1
9
10# Negative numbers and zero
11assert abs(-5) == 5
12assert (0 or "fallback") == "fallback" # 0 is falsy, watch this
13
14# Largest and smallest allowed sizes from the problem limits
15# (a loop that is fine for n=10 may time out for n=1_000_000)
Common edge cases by problem type:
- Arrays and strings: empty, length one, all identical, already sorted, reverse sorted.
- Numbers: zero, negatives, the maximum and minimum allowed values, integer overflow (Python ints are unbounded, so this is usually about time, not value).
- Graphs and trees: a single node, no edges, a cycle, a disconnected component.
- Search and boundaries: the target at the first index, the last index, and not present at all.
A workflow that catches bugs early#
Put it together: read the input carefully, write two or three asserts covering a normal case plus the empty and single-element cases, run them, and only reach for breakpoint() when a print trace is not enough. This loop is fast and catches the mistakes that cost points.
Where to go next#
Solid input handling and a habit of quick tests will save you across the whole curriculum. When a solution is correct but slow, revisit complexity and performance to find the bottleneck, then keep practicing on the algorithms.