When the interview lets you run code, a small table-driven test harness is the fastest way to show your solution works. Paste it under your solution, fill in a handful of cases grouped by category, and run it. It shows you think about correctness systematically, and when a case fails it prints exactly what went wrong.
Picking test cases#
Aim for 6–10 cases that each test something different:
| Category | Examples |
|---|---|
| Happy path | The examples from the problem statement |
| Smallest inputs | Empty input, a single element |
| Boundaries | Answer at the very start or end; maximum or minimum values |
| Repeats | All elements the same, many duplicates, zeros |
| Signs | Negative numbers, mixed signs |
| No answer | No valid result exists: does it return 0, -1, []? |
| Problem-specific traps | Whatever made the naive approach wrong (e.g. negatives breaking a sliding window) |
Write a short description for each case so a failure explains itself.
The template#
Paste this below your solution. It runs every case, reports crashes separately from wrong answers, and prints a summary.
def run_tests(func, test_cases):
"""test_cases: list of (args_tuple, expected, description)."""
bar = "=" * 10
name = func.__name__
print(f"\n{bar} Running {len(test_cases)} tests for {name} {bar}")
passed = 0
for i, (args, expected, desc) in enumerate(test_cases, start=1):
try:
result = func(*args)
except Exception as e: # crash: report it and keep going
print(f"ERROR case {i}: {desc}")
print(f" {type(e).__name__}: {e}")
continue
if result == expected:
print(f"PASS case {i}: {desc}")
passed += 1
else:
print(f"FAIL case {i}: {desc}")
print(f" input: {args}")
print(f" expected: {expected}")
print(f" got: {result}")
print(f"{bar} {passed}/{len(test_cases)} passed {bar}")
# Usage:
# sol = Solution()
# run_tests(sol.your_method, [
# ((arg1, arg2), expected, "description"),
# ...
# ])
A few design choices make this robust:
- Calling the function outside the comparison means a crash in one case is reported, not mistaken for a failed assertion, and the run continues.
- Passing the bound method (
sol.subarraySum) keeps the harness generic across problems. - If the order of the output doesn't matter, compare
sorted(result)tosorted(expected)in the case or wrap the function.
Example: Subarray Sum Equals K#
Here is the harness applied to a real problem, with cases grouped by category.
from collections import defaultdict
from typing import List
class Solution:
def subarraySum(self, nums: List[int], k: int) -> int:
seen = defaultdict(int)
# the empty prefix, so subarrays starting at index 0 count
seen[0] = 1
prefix = count = 0
for x in nums:
prefix += x
count += seen[prefix - k]
seen[prefix] += 1
return count
test_cases = [
# Happy path
(([1, 1, 1], 2), 2, "two overlapping [1,1] subarrays"),
(([1, 2, 3], 3), 2, "[1,2] at the start and [3] at the end"),
# Smallest inputs
(([1], 1), 1, "single element equals k"),
(([5], 1), 0, "single element does not equal k"),
(([], 0), 0, "empty array"),
# Repeats / zeros
(([0, 0, 0], 0), 6, "every subarray of zeros sums to 0"),
# Negatives: why sliding window fails here
(([1, -1, 1], 1), 3, "running sum goes down and up"),
(([1, -1, 0], 0), 3, "negatives and zeros mixed"),
# No answer
(([1, 2, 3], 100), 0, "no subarray reaches k"),
]
run_tests(Solution().subarraySum, test_cases)
(run_tests is the function from the template above.)
When you can't run code#
Use the same table as a checklist and dry-run it by hand: trace the variables for one happy-path case, then talk through each edge case. Saying "with an empty array the loop never runs, so we return 0" is often enough.
Key takeaways#
- Group cases by category: happy path, smallest inputs, boundaries, repeats, signs, no answer.
- Give each case a description so failures explain themselves.
- Call the function separately from the comparison so crashes are reported clearly.
- Include the case that breaks the naive approach; it shows you know why your solution works.
- Without a runner, dry-run the same table out loud.