Errors, Edge Cases & Testing in the Room
Juniors code the happy path; seniors enumerate what breaks first. The edge-case checklist as a habit, raise-with-context and exception chaining, assert vs exceptions and the -O caveat, a five-minute inline self-test, and property thinking without a framework — worked on one binary search, every snippet run in real Python.
Concept · Languages & Runtimes. The source ↗
A free, interactive, animated visual explainer of Errors, Edge Cases & Testing in the Room — built to be understood, not skimmed.
Questions
- How do I find edge cases in a coding interview?
- Run a fixed checklist over the problem before you write the solution, out loud, so it becomes a habit instead of an afterthought. The five categories that catch almost everything: size (empty, one element, many), value (None, zero, negative), duplicates, text (unicode, leading/trailing whitespace, case), and boundaries (the first element, the last element, and one step past each end). For a binary search over a sorted list that means testing [], [5], a duplicate list like [5, 5, 5], a target below the smallest and above the largest element, and the exact first and last values. Most bugs live at empty, at duplicates, and one index past the boundary — the inputs the given example never includes.
- Should I use assert or raise an exception for bad input in Python?
- Different jobs. Raise an exception (ValueError, TypeError) for bad input — the caller can send garbage, and a raise is a real, guaranteed part of your contract. Use assert only for conditions you believe are impossible — an internal invariant that, if false, means your own code is broken, not the caller's. The reason the distinction matters is that assertions are removed when Python is run optimized: the docs state that "the current code generator emits no code for an assert statement when optimization is requested at compile time" (the -O flag, which sets __debug__ to False). So an assert guarding user input silently disappears in production and the check is gone. Validate input with raise; document invariants with assert.
- What is exception chaining with raise ... from in Python?
- It preserves the original error as the cause of a new one instead of hiding it. The tutorial says "to indicate that an exception is a direct consequence of another, the raise statement allows an optional from clause" — as in raise ConfigError("no config") from err. The traceback then shows both, joined by "The above exception was the direct cause of the following exception," so you keep the low-level detail (a FileNotFoundError) while raising a meaningful, domain-level error. This is why you almost never want a bare except: PEP 8 says to "mention specific exceptions whenever possible instead of using a bare except: clause," because a bare except "will catch SystemExit and KeyboardInterrupt exceptions, making it harder to interrupt a program with Control-C, and can disguise other problems."
- How do I test my code on a coding platform without pytest?
- Put inline assertions under an if __name__ == "__main__": block at the bottom of the file — they run when you press Run and cost nothing to write. For a binary search, six lines cover the checklist: assert binary_search([], 5) == -1, assert binary_search([5], 5) == 0, assert binary_search([5], 3) == -1, assert binary_search([1, 3, 5, 7, 9], 1) == 0 (first), assert binary_search([1, 3, 5, 7, 9], 9) == 4 (last), assert binary_search([1, 3, 5, 7, 9], 4) == -1 (absent in range). If all six pass, print a line so you see it; if one fails, you get the AssertionError before the interviewer does. That five-minute habit is the difference between "looks right" and "is right."
- What is property-based testing and can I do it without a framework?
- It is testing a property that must hold for all inputs, rather than checking hand-picked examples. Hypothesis, the Python library for it, lets you "write tests which should pass for all inputs in whatever range you describe, and let Hypothesis randomly choose which of those inputs to check — including edge cases you might not have thought about." You do not need the framework to think this way: seed a random generator, build a few hundred random sorted lists and targets in a loop, and check the invariant against a slow oracle — if binary_search returns -1 the target must not be in the list, otherwise arr[idx] must equal the target. A loop of ten lines finds the boundary and off-by-one bugs your six fixed tests can miss.