Hypothesis Patterns -- Property-Based Testing¶
Why This Matters in Interviews¶
Reaching for Hypothesis during a coding interview signals testing sophistication well beyond unit tests. Three things it demonstrates:
-
You test the specification, not specific cases. Instead of checking that
sort([3, 1, 2]) == [1, 2, 3], you assertresult == sorted(result)for any generated input. This catches edge cases you'd never think to write by hand. -
You know the oracle pattern. Compare your optimized solution against a known-correct brute-force reference. For example, validate
top_k_frequent(bucket sort, O(n)) againstCounter.most_common(k)--- the obvious but slower approach. One line of property, thousands of test cases. -
You can articulate invariants. "The output is always sorted", "every returned element exists in the input", "length never exceeds k" are properties, not examples. Hypothesis tests them across hundreds of random inputs automatically.
How the Tests Are Wired Up¶
The source module below (src/concepts/hypothesis_patterns.py) defines two
systems-under-test: SortedList and BoundedCounter. The real tutorial
lives in the test file (tests/concepts/test_hypothesis_patterns.py), which
walks through six Hypothesis patterns with runnable, heavily-commented code:
| Pattern | What It Does | When to Reach for It |
|---|---|---|
@composite |
Build complex test data step by step | Inter-dependent constraints (e.g., pre-filled data structures) |
RuleBasedStateMachine |
Stateful testing with an oracle | Sequences of operations that must maintain invariants |
@example |
Pin specific inputs | Regression tests, known edge cases |
@settings |
Control intensity | CI (high) vs local dev (fast) profiles |
st.builds |
Auto-generate dataclass instances | Any __init__ that takes simple args |
st.recursive |
Generate nested/tree structures | JSON-like data, ASTs, recursive types |
Quick Interview Demo¶
To demonstrate PBT mastery in under 2 minutes during an interview:
from hypothesis import given
from hypothesis import strategies as st
@given(data=st.lists(st.integers(), min_size=1))
def test_top_1_matches_counter(data: list[int]) -> None:
"""Oracle pattern: compare O(n) bucket sort against Counter."""
from collections import Counter
result = top_k_frequent(data, 1)
expected_freq = Counter(data).most_common(1)[0][1]
assert Counter(data)[result[0]] == expected_freq
This single test generates hundreds of random inputs and validates your implementation against the stdlib oracle. No hand-written edge cases needed.
Systems Under Test¶
hypothesis_patterns ¶
Systems-under-test for Hypothesis property-based testing patterns.
This module provides two deliberately simple but non-trivial classes that are ideal targets for Hypothesis strategies and stateful testing:
SortedList— a list that maintains sorted order viabisectBoundedCounter— an integer counter clamped to [min_val, max_val]
The real tutorial lives in the corresponding test file
(tests/concepts/test_hypothesis_patterns.py), which demonstrates
@composite, RuleBasedStateMachine, @example, settings
profiles, st.builds, and st.recursive.
References¶
bisectmodule — https://docs.python.org/3.14/library/bisect.html- Hypothesis docs — https://hypothesis.readthedocs.io/en/latest/
SortedList ¶
A list that maintains its elements in sorted order at all times.
Insertions use bisect.insort for O(n) insertion with O(log n)
position finding. Searching uses bisect.bisect_left for
O(log n) membership checks.
sl = SortedList() sl.insert(3) sl.insert(1) sl.insert(2) sl.to_list() [1, 2, 3] sl.search(2) True sl.search(4) False
Source code in src/concepts/hypothesis_patterns.py
insert ¶
Insert val in sorted position.
sl = SortedList() sl.insert(5) sl.insert(2) sl.to_list() [2, 5]
Source code in src/concepts/hypothesis_patterns.py
search ¶
Return True if val exists in the list.
Uses binary search via bisect_left — O(log n).
sl = SortedList() sl.insert(10) sl.search(10) True sl.search(99) False
Source code in src/concepts/hypothesis_patterns.py
remove ¶
Remove one occurrence of val.
Raises ValueError if val is not present.
sl = SortedList() sl.insert(1) sl.remove(1) sl.to_list() []
Source code in src/concepts/hypothesis_patterns.py
to_list ¶
Return a copy of the internal sorted list.
sl = SortedList() sl.insert(3) sl.insert(1) sl.to_list() [1, 3]
BoundedCounter
dataclass
¶
An integer counter clamped between min_val and max_val.
Attempting to increment past max_val or decrement past min_val
raises ValueError.
bc = BoundedCounter(min_val=0, max_val=3) bc.increment() bc.increment() bc.value 2 bc.decrement() bc.value 1
Source code in src/concepts/hypothesis_patterns.py
increment ¶
Add 1, raising ValueError if at max_val.
bc = BoundedCounter(min_val=0, max_val=1) bc.increment() bc.increment() Traceback (most recent call last): ... ValueError: counter at maximum (1)
Source code in src/concepts/hypothesis_patterns.py
decrement ¶
Subtract 1, raising ValueError if at min_val.
bc = BoundedCounter(min_val=0, max_val=5) bc.decrement() Traceback (most recent call last): ... ValueError: counter at minimum (0)