Expert: advanced patterns and interviews · Interview strategy and mock problems · topic 59 of 61 · 35 min
Testing, edge cases and debugging under pressure
Explanation
Why testing is part of the answer
Interviewers watch whether you find your own bugs. A candidate who traces the code, spots "this misses the last window" and fixes it calmly scores better than one who writes the same code and says "done". In online assessments, hidden tests check exactly the edge cases that people forget.
An edge-case checklist
| Case | Example input | What can break |
|---|---|---|
| Empty input | [], "", {} | nums[0] raises IndexError; max([]) raises ValueError |
| One item | [7], "a" | Loops that start at index 1, pairs that need two items |
| Two items | [1, 2] | Two pointers meeting, mid calculations |
| Duplicates | [2, 2, 2] | Strict versus non-strict comparisons, counting twice |
| All the same | [5, 5, 5, 5] | "Second largest" has no answer |
| Negatives and zero | [-3, 0, -1] | Starting best at 0, dividing by zero, products |
| Sorted / reverse sorted | [1, 2, 3], [3, 2, 1] | Worst cases for naive quicksort, monotonic stacks |
| Very large n | 10⁵ or 10⁶ items | O(n²) time-outs, deep recursion (RecursionError) |
| Very large values | 2³¹ − 1 in Java or C++ | Integer overflow (Python ints do not overflow) |
| No valid answer | Target not present | Returning None versus −1 versus []: ask which |
Dry runs with a table of variables
A dry run means executing your code by hand on a small input, writing each variable after every step. It is slow, but it finds off-by-one errors that reading never finds. Choose the smallest input that reaches the tricky part, for example a target at the very end of the list.
| step | lo | hi | mid | nums[mid] | action |
|---|---|---|---|---|---|
| 1 | 0 | 3 | 1 | 3 | 3 < 7, so lo = 2 |
| 2 | 2 | 3 | 2 | 5 | 5 < 7, so lo = 3 |
| 3 | 3 | 3 | — | — | while lo < hi is False: returns −1. Bug! |
The table above traces a binary search for 7 in [1, 3, 5, 7] written with while lo < hi and hi = len(nums) − 1. When lo and hi meet at index 3, the last candidate is never checked. The fix is while lo <= hi. Without the dry run, this bug hides until a hidden test finds it.
Off-by-one checks
- range(a, b) stops BEFORE b. If you need the last index, the end is len(nums), not len(nums) − 1.
- In a sliding window of size k, the windows end at indexes k − 1 to n − 1: that is n − k + 1 windows. Count them on a 5-item example.
- With "inclusive" bounds (lo <= hi), move with mid + 1 and mid − 1. With "exclusive" bounds (lo < hi, hi = len), move with lo = mid + 1 and hi = mid. Do not mix the two styles.
- Slices: s[i:j] has j − i characters.
Quick asserts
In an editor that runs code, write three to five one-line checks right under your function. They take a minute and protect you while you change things.
def second_largest(nums):
first = second = None
for x in nums:
if first is None or x > first:
first, second = x, first
elif x != first and (second is None or x > second):
second = x
return second
assert second_largest([3, 1, 4]) == 3
assert second_largest([5, 5]) is None
assert second_largest([]) is None
assert second_largest([-2, -1]) == -2
print("all tests passed") # Output: all tests passedReading error messages
Read a Python traceback from the BOTTOM: the last line names the error, and the line above it shows where it happened in your code. The common ones in interviews:
- IndexError: list index out of range. An index went past the end, often i + 1 in a loop over range(len(nums)).
- KeyError: a dictionary key is missing. Use d.get(key, default) or collections.defaultdict.
- TypeError: 'NoneType' object is not subscriptable. A function returned None (often a forgotten return) and you indexed it.
- RecursionError: maximum recursion depth exceeded. Missing base case, or recursion too deep for Python; use a loop or an explicit stack.
- UnboundLocalError: a variable is used before it is set inside the function, often a counter you forgot to initialise.
Common mistakes
- Testing only the example from the question. Fix: always add empty, one item and one tricky case from the checklist.
- Changing code randomly until the output looks right. Fix: dry-run, find the exact line where a variable goes wrong, then fix that line.
- Mutable default arguments in Python, such as def f(x, seen=[]), which keep values between calls. Fix: use seen=None and create the list inside.
- Panicking at a red error. Fix: read the last line and the line number, say what it means aloud, and check that line.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Testing, edge cases and debugging under pressure · 1/9
This lesson is about finding your own bugs quickly, even under interview pressure.
Prefer a video by a teacher? Find free videos on “coding interview edge cases dry run debugging tips off by one errors” on YouTube ↗
In real life: A UPI-style payment splitter bug
A team builds a feature that splits a restaurant bill among friends. It works in every demo, then fails in production for a group of one person: the code assumed at least two people and divided the remainder by (people − 1). The bug was found in minutes once someone wrote down the edge-case checklist (one item, zero, large values) and added a test for each. Payment and banking teams in India and elsewhere write tests for exactly these cases because a rare bug can still touch many transactions.
def split_bill(total_paise, people):
if people <= 0:
raise ValueError("need at least one person")
share, extra = divmod(total_paise, people)
# the first 'extra' people pay one paisa more, so the shares add up exactly
return [share + 1 if i < extra else share for i in range(people)]
print(split_bill(1000, 3))
print(split_bill(999, 1))
print(sum(split_bill(100001, 7)) == 100001)Practice (0/3 done)
Fix the bug. binary_search(nums, target) should return the index of target in the sorted list of DISTINCT numbers nums, or -1 if it is not there. The starter looks right but fails on some inputs. Dry-run it on [1, 3, 5, 7] with target 7, find the bug and fix it (keep the O(log n) binary search).
Fix the bugs (there are two). max_window_sum(nums, k) should return the largest sum of any k CONSECUTIVE numbers in nums, in O(n) time with a sliding window. If k <= 0 or k > len(nums), return None. Test the starter on [1, 2, 3, 4, 5] with k = 2 (expected 9) and with k = 0 (expected None).
Fix the bug. collect_evens(nums, result=...) appends every even number of nums to result and returns result. If the caller passes a list as result, append to THAT list; if not, start with a NEW empty list on every call. The starter passes the first call but goes wrong on later calls. Keep the same function name and parameters.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Mock problems: design-style data structuresInterview strategy and mock problems · same module
- Recognising the patternInterview strategy and mock problems · same module
- Unit testing: checking one piece at a timeBackend Development · Testing and API documentation
- Writing data code that others can trustData Science, Machine Learning and AI · Python for data
- Testing your codePython Programming · Algorithms, data structures and testing
- assert, logging and testing C codeC Programming (from zero to systems) · Debugging and tools
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which input is the BEST extra test for a "find the second largest" function?
Answer: [5, 5, 5]
All-equal values have no second distinct value, a case that is easy to get wrong.
How many windows of size k are there in a list of n items (k ≤ n)?
Answer: n − k + 1
Windows end at indexes k − 1 to n − 1, which is n − k + 1 positions.
Python shows a long traceback. Where should you look first?
Answer: The last line (error type and message), then the line number in your code
The last line says what went wrong; the frame above it says where.
In Java, why write mid = lo + (hi − lo) / 2 instead of (lo + hi) / 2?
Answer: lo + hi can overflow int for very large indexes
If lo + hi exceeds 2³¹ − 1 it wraps to a negative number; hi − lo never overflows here.
Your function returns None and the caller does result[0]. Which error appears?
Answer: TypeError: 'NoneType' object is not subscriptable
Indexing None is a TypeError; it usually means a missing return statement.
Which of these is an edge case for a discount function?Related
Answer: A cart total of 0 rupees
Edge cases sit at the limits: empty, zero, negative, very large. Zero is a classic one.
With an inclusive range (hi = len(nums) − 1), why must the loop be while lo <= hi?Related
Answer: So a range with one item is still checked
When lo == hi one item remains; while lo < hi would stop without checking it.
From Binary search
Why should the average of an empty list return None rather than 0?Related
Answer: 0 is a real value and could be mistaken for a true average of zero
Returning None makes "no data" visible, while 0 would quietly look like a real answer.
Which is an edge case for a function that checks if marks pass (pass mark 35)?Related
Answer: Marks of exactly 35
Exactly at the limit is the border, where off-by-one mistakes (> instead of >=) show up.
From Testing your code
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 2 more topics in “Interview strategy and mock problems” before its exam: Mock problems: design-style data structures, Your study plan and the job hunt.
- Module 12 exam: Interview strategy and mock problems · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- 61 topics left in the course before the final exam.