You do not need to understand every line
Professional programmers do not read code the way you read a novel. They read to answer a question. You can learn that in an afternoon, and it is the single skill that turns AI output from a gamble into a tool.
The question is almost always: what happens when someone does this thing?
Trace one action, end to end
Pick a single thing a user does. Follow it through the code. For a booking form:
- Find the button. Search the project for the button's text — "Book now". You land in a file that draws the form.
- Find what the button calls. Something like
onSubmit={handleSubmit}oraction="/api/bookings". Follow it. - Find where it goes on the server. A file like
app/api/bookings/route.ts. Read that function top to bottom. It is usually thirty lines. - Find where it touches the database. An
insertorINSERT INTO. That is the moment something becomes permanent. - Find where it is read back. Whatever query the bookings page runs.
You now understand that feature. Not the whole codebase — that feature. Do this three times and the shape of the project stops being a mystery.
Four questions for any chunk of code
- What goes in?
- What comes out?
- What does it change that survives after the request ends — the database, a file, an email that was sent?
- What happens when it fails?
That fourth one finds most problems.
Things to look for, specifically
Errors that go nowhere.
try {
await saveBooking(booking)
} catch (e) {
// ignore
}The save can fail and the user still sees "Booked." This shows up constantly, because swallowing errors makes the code stop complaining, and code that stops complaining looks finished.
Checks that happen in the browser only. If the only thing stopping a bad action is a hidden button or a disabled input, there is no check at all. More on this in lesson 8.
Values typed straight into the code. A price, a tax rate, a phone prefix, a URL like http://localhost:3000 sitting inside a function. It works on your machine and breaks the moment you deploy.
Code nobody calls. Agents leave orphans behind. If you cannot find anything that uses a function, it may be dead — or the thing you thought was running is a different function with a similar name.
A second copy. Ask the model to fix a bug twice and you can end up with formatDate and formatDateNew, one of which is still wrong and still used somewhere.
Read the diff, not the file
After a change, look at what changed rather than re-reading everything. git diff in the terminal, or the source control panel in your editor, shows you added lines in green and removed in red. Ninety percent of your reading should be diffs. If a request to add a cancel button produced changes in nine files, something happened that you did not ask for, and you want to know what before you move on.
Make the model explain, then check the explanation
"Explain what this function does, line by line, as if I have never seen this framework" is a genuinely good use of the tool. Explanations are easier to evaluate than code.
But an explanation is a second generation, not a source of truth. It can describe code the model intended to write rather than the code on disk. So verify claims against reality, and prefer verifying by behaviour over verifying by text.
If it says "I added validation so invalid emails are rejected," do not search the file for the word "validate." A comment mentioning validation will match. Instead, open the form and submit a@b, then submit nothing at all, and watch what the app does.
Searching the source tells you what the code says about itself. Using the app tells you what it does.
A five-minute review you can actually do
After each change:
- Read the diff. Every file. Skim is fine.
- Anything you do not recognise, ask: "why did you change this file?"
- Find the failure path. What happens when the input is empty, wrong, or duplicated?
- Use the feature. Then use it wrongly on purpose.
- Commit if it works.
You will catch more than you expect, because most AI bugs are not subtle. They are loud, and nobody looked.
Before you move on