Narrowing the search
halving instead of reading everything
What it is
When it isn't clear where the error is, reading all the code from the top is the worst way to find it. There is a more reliable technique: split the data's path in half, check the middle, and throw away the half where everything is fine.
That way the search area halves at every step. From a thousand lines to the right one — ten checks.
What it looks like in code
On a live example
A chain of four functions: load the rows, parse them, turn them into numbers, compute the average. It crashes somewhere inside.
def load_rows(raw: str) -> list[str]: ...
def parse_rows(rows: list[str]) -> list[dict]: ...
def to_numbers(items: list[dict]) -> list[dict]: ...
def average(items: list[dict]) -> float: ...
print(average(to_numbers(parse_rows(load_rows(RAW)))))Step 1. Print in the middle. Not at the start and not at the end — exactly halfway along the chain:
rows = load_rows(RAW)
items = parse_rows(rows)
print("after parse_rows:", items)after parse_rows: [{'city': 'Oslo', 'temp': '4'},
{'city': 'Bergen', 'temp': '7'},
{'city': 'Tromso', 'temp': '-2.5'}]The data is fine. So the first half of the chain is clean and can be forgotten. Two functions left out of four.
Step 2. Halve the remainder. Check to_numbers on that same data, item
by item:
for it in items:
try:
int(it["temp"])
except ValueError as e:
print("fails on:", it, "->", e)fails on: {'city': 'Tromso', 'temp': '-2.5'} -> invalid literal for int() with base 10: '-2.5'Two checks — and both the line and the specific data are found. The cause is immediately visible: the code assumed whole-number temperatures and the data has a fractional one.
Where to split
| What we are after | Where the middle is |
|---|---|
| An error in a chain of functions | between the functions, by the data |
| An error in a long function | between the logical blocks |
| An error that appeared after edits | git log — between commits |
| An error on one item in a thousand | by half of the list |
The third row is a technique of its own: if the code worked yesterday and
broke today, what you halve is not the code but the commit history. Git has
git bisect for that, though switching to a commit in the middle by hand is
often enough.
What to tell the AI
Here is a chain of four functions; it crashes somewhere inside.
Where should I put one print to split the chain in half?
Don't fix the code, just name the place.I printed the data after parse_rows and it is fine.
So the error is further on. What should I check next?A dialogue like this saves time: the AI is good at pointing out where the middle is, and you are the one who runs it and looks.
What to look for
Printing at the start and the end instead of the middle. The start is usually fine and the end is usually broken — neither check narrows anything.
The AI offering fixes before the area is narrowed. The typical answer to "it doesn't work" is three hypotheses at once. Any of them may be right, and all of them have to be checked. Narrow it yourself, then ask for a fix.
The area growing instead of shrinking. If every check adds new suspects, you are checking consequences rather than data. Go back to "is the data still intact here?".
Debugging without notes. You checked three places, forgot the results, and check them again. Write one line each: "after parse_rows — fine".
Cheat sheet
Halve the data's path, check the middle, discard the clean half.
The question at each step: "is the data still intact here?"
From a thousand lines to one — ten checks.
Broke after edits — halve the commit history, not the code.
Printing at the start and the end narrows nothing.
Write the results down, or you will go in circles.