Verifying Behavior and Finding the Edge Cases AI Misses
Learning Objectives
- You can move from “does this code look right?” to “does it behave right on every input it might see?”
- You can construct a systematic list of edge cases for a piece of branching or looping code.
- You can identify the inputs that break a piece of AI-generated code that works on the obvious case.
In Part 1, you learned the foundational AI skill: reading code carefully and asking what it actually does. This part’s skill is the natural follow-up. Once you can read code and predict its behavior on a given input, the next question is: what about all the other inputs? Especially the ones the person who wrote the code did not think about.
This chapter is about being systematic with that question. The skill applies to all code, but it matters also for AI-generated code, because AI-generated code often works on the obvious cases, but breaks quietly on others.
The Stance Shift
In the previous part, when you read a piece of code, the question was: “what does this do?”
In this part, the question grows a second half: “what does this do on every input it might actually see?”
Surface-level checking — “does the code run? does it produce output? does the output look right?” — is what most people do. It catches the obvious bugs. The bugs that hide behind surface-level checking are the ones the test inputs did not happen to expose.
When you write code yourself, you have a small advantage here: you know what you were thinking, so you have at least some idea of what cases you might have missed. When you read code that an AI generated, you do not have that. The AI may have considered cases you wouldn’t, or skipped cases you would have remembered without thinking. You have to be more systematic than you might be with your own code.
The good news is that the systematic version is not hard. It is a short checklist applied to every piece of branching or looping code.
A Checklist of Edge Cases
For any function or loop that processes a list or a value, run through this list:
| Case | Question to ask |
|---|---|
| Empty | What if the list is empty? What if the input string is empty? |
| Single element | What if the list has exactly one item? |
| Boundary values | What happens exactly at the threshold of each comparison? < vs <=? |
| All equal | What if every item in the list is the same? |
| No match | What if a search loop finds nothing? |
| Zero and negatives | What if a value is 0? What if it is negative when you expected positive? |
| Unusual input format | What if the user typed Done instead of done? Or done with a trailing space? |
The point is not that every case applies to every program. The point is to scan the checklist every time, so the cases that do apply get noticed before they bite you.
The next three sections walk through three short pieces of AI-generated code. Each works on the case the AI was clearly thinking about. Each breaks on one or more cases from the checklist.
Edge-case checklist
0 / 5 points
Which input is most useful for checking whether an average-over-a-list program handles a common edge case?
Example 1: The Plausible-Looking Max Function
Suppose you ask an AI assistant: “write Python code that prints the longest session in a list of minutes.” It produces this:
minutes_list = [45, 30, 90, 60, 75]
longest = 0for minutes in minutes_list: if minutes > longest: longest = minutes
print(f"Longest: {longest}")This program prints Longest: 90. The AI tests it in its head, sees the right answer, and is satisfied. So might you.
But run the checklist. Empty list: what if minutes_list = []? The loop runs zero times, longest stays at 0, and the program prints Longest: 0. That is wrong — the longest of nothing is not 0, it is undefined. The program should probably say something like “No sessions yet” instead.
Zero or negative values: what if every session is somehow 0? The condition minutes > longest is 0 > 0, which is False. So longest never gets updated, and the program prints Longest: 0. Misleading, but in this case it happens to be the right number. What if the list contained negative numbers? Then longest = 0 is a wrong starting value, and the result would be 0 for any list of negative numbers — also wrong.
The fix here is the version from Counting, Accumulating, and Searching: handle the empty case explicitly, and start longest at minutes_list[0] instead of 0.
The general lesson is that this kind of bug does not show up in a single test. The AI’s version “works” if you only feed it lists like the one it had in mind. The checklist is what catches the rest.
Critique a plausible max program
0 / 10 points
An AI assistant suggests this code:
scores = []
best = 0for score in scores: if score > best: best = score
print(f"Best score: {best}")Explain why the code can look plausible, what it gets wrong for the empty list, and how the program should handle that case.
Your answer should include:
- why the loop seems reasonable for a non-empty list of positive scores,
- what happens when
scoresis empty, - why
Best score: 0is misleading, - and an explicit empty-list check or fallback message.
Example 2: The Off-by-One Classifier
Suppose you ask an AI assistant to write code that classifies a session as short, solid, or long, with the thresholds at 30 and 90 minutes. It produces this:
minutes = int(input("Minutes: "))
if minutes < 30: print("Short.")elif minutes < 90: print("Solid.")else: print("Long.")Looks fine. Run it with 15, 60, and 120. You get Short., Solid., Long. Perfect.
Now run the boundary values check. What does the program do at exactly 30? 30 < 30 is False, so we move on. 30 < 90 is True, so the answer is Solid. What about exactly 90? 90 < 30 is False. 90 < 90 is False. So we fall through to else, and the answer is Long.
Did the AI mean for 30 to be “Short” or “Solid”? Did it mean for 90 to be “Solid” or “Long”? The code makes a choice — 30 is “Solid” and 90 is “Long” — but the original request didn’t say anything about the boundary, and the AI just picked one. If you intended 90 to be the largest “Solid” session and only sessions over 90 to be “Long”, this code is wrong, and a casual reading would not catch it.
This kind of bug is everywhere in AI-generated branching code. The thresholds are usually approximately right. The choice between < and <= at the boundaries is usually a coin flip. The AI cannot read your mind about whether you wanted 30 to be a short session or a solid one, so it just picks. Your job, when reading code like this, is to look at every comparison operator and ask: “is this the right one?”
Fix the boundary classifier
0 / 10 points
Ask the user for a number of study minutes. Convert the answer to an integer.
Print exactly one of these messages:
Short.for values less than or equal to30Solid.for values greater than30and less than or equal to90Long.for values greater than90
Example input:
30Required output text:
Short.Example input:
90Required output text:
Solid.Example 3: The “Type ‘done’ to Quit” Loop
Suppose you ask an AI assistant for a loop that asks the user for subjects until they type done. You get:
subjects = []
while True: subject = input("Subject (or 'done' to quit): ") if subject == "done": break subjects.append(subject)
print(f"Logged {len(subjects)} subjects.")This works on the obvious case. Run it, type Math, Physics, done, and the program prints Logged 2 subjects.
Now run the unusual input format check. What if the user types Done with a capital D? "Done" == "done" is False, so the loop does not exit. The program adds "Done" to the list of subjects and keeps asking. The user is confused; the AI did not consider it.
What if the user types done with a trailing space? Same problem: "done " == "done" is False, and "done " ends up in the subject list.
The fix is one line — normalize the input before comparing:
while True: subject = input("Subject (or 'done' to quit): ").strip().lower() if subject == "done": break subjects.append(subject)This is the kind of fix that is easy to miss because the original code runs without errors. There is no crash. There is no traceback. The user just gets a confusing experience. Only the checklist surfaces this kind of bug.
A Reference Table of Failure Modes
Bringing the three examples together, here are the most common control-flow failure modes in AI-generated code. Treat this as a quick reference when reviewing loop and branch code from any source:
| Failure mode | Symptom | Where to look |
|---|---|---|
| Wrong starting value for accumulator | Wrong answer on empty list or all-negative input | Line before the loop |
Off-by-one in < vs <= | Wrong branch at the exact threshold | Comparison operators inside if |
Missing else | Silent gap when no branch matches | End of if/elif chain |
| No empty-list handling | Crash or misleading result on empty input | Lines that use the loop’s result |
Off-by-one in range() | Last item skipped or extra iteration | range() arguments |
Forgotten break | Search loop continues after finding | Inside the if that should signal success |
| Unnormalized string comparison | Quit signal doesn’t work with different cases or spacing | if subject == ... |
| Infinite loop | Program never ends | while condition that depends on a variable never updated inside the loop |
A useful habit is to keep this table in mind whenever you read AI-generated code. You do not need to check every row on every program. You need to check the rows that are relevant to what the program is doing.
Below, you get to practice asking for code from an AI assistant. When you get the code, study it to identify any edge cases it misses. If you do not find any, submit the program for evaluation. Running the AI-generated code does not currently properly work, but you can try it in the sandbox.
Prompt a robust sentinel loop
0 / 10 points
Write a prompt that asks a large language model to create a Python program.
The generated program should:
- repeatedly ask for a subject,
- stop when the user types
done, even if they typeDoneordonewith extra spaces, - store every non-quit subject in a list,
- and print how many subjects were logged.
The generated program will be tested with these inputs:
MathArtDoneIts output must include 2. It will also be tested with done as the first input, and then its output must include 0.
Your prompt should explicitly tell the model to:
- use a
whileloop, - check for
donebefore appending the subject, - use
.strip().lower()when checking the quit signal, - and print the final count after the loop.
Ask for Python code only, with no explanation.
Where You Are in the Nine-Skill Arc
In Part 1, the AI skill was reading: understanding what a piece of code actually does. This part’s skill is verifying: confirming that what it does is what it should do, on every input it might see.
These two together form the foundation of every later AI-collaboration chapter:
| Part | AI skill |
|---|---|
| 1 | Reading code you didn’t write |
| 2 | Verifying behavior and finding edge cases |
| 3 | Specifying what you want — turning intent into prompts |
| 4 | Detecting plausible-but-wrong code |
| 5 | Comparing approaches — yours and the AI’s |
| 6 | Critiquing data designs |
| 7 | Working with AI on libraries you’re still learning |
| 8 | Critiquing AI-suggested visualizations |
| 9 | Iterating with AI on a real project, and recognizing when to stop |
You can think of Part 1 as “read the code”. Part 2 as “stress-test the code”. Together they answer: does this code do what I think it does, and does it keep doing it when the input is unusual? Every later part builds on this base.
Verifying Is Cheap
The same stance from Part 1’s reading chapter still applies: you are being a programmer first, regardless of who or what produced the code. What is new in this part is that the checklist is now what you do instead of trusting. A bug found by the checklist is a small specific win. A checklist that comes up empty is also useful — the code held up, and you can use it with confidence. The cost of running it is low, and as the code in later parts grows more complex, the trade-off only gets more favorable.
Summarize the verifying skill
0 / 10 points
In your own words, summarize the Week 2 AI-collaboration skill: verifying behavior and finding edge cases.
Include one concrete example of an edge case from this part, such as an empty list, a boundary value, or a quit signal with unusual capitalization or spacing.
Your answer should include:
- the difference between reading what code does and verifying whether the behavior is correct,
- why one obvious successful input is not enough,
- one concrete edge case from this part,
- and why this habit matters when reviewing AI-generated code.
Check Your Understanding
- Why is “the code runs and produces output” not the same as “the code is correct”?
- List three cases from the edge-case checklist that you would check on any loop that processes a list.
- In Example 2, what change would you make to the comparison operators if you wanted
30to be classified as “Short” and90to be classified as “Solid”? - In Example 3, why does
subject == "done"fail when the user typesDone? What is the fix? - In one sentence, what is the AI-collaboration skill this part was about?
AI Study Aid
Create a chapter diagram
We're looking into to what extent AI-generated diagrams could help with studying.
Use this study aid to generate an AI-generated visual summary of the material you just studied.
Each diagram style emphasizes the content in a different way, so you can choose the focus
that feels most useful.
Using the diagram generator is voluntary and does not affect your course progress. We encourage
you to try it out and see if it helps, but it's totally up to you! Your answers help us understand
how to make better study aids in the future.