How to review code an AI wrote

You asked for a function, it wrote one, you ran it and it works. The code looks tidy: clear names, error handling, even comments.
And that is the problem. Code written by an AI always looks good. It does not
carry the signals you learned to spot in a person’s rushed code: variables called
temp2, two-hundred-line functions, comments saying “fix this later”. It is tidy
even when it is wrong.
Reviewing it is not a matter of reading more carefully. It is a matter of looking at different things. These six, in this order.
1. Does it do exactly what you asked, or something similar?
This is the most common mistake and the easiest to miss, because the result looks a lot like what you wanted.
You asked for “sort the users by registration date”. You get back:
users.sort((a, b) => a.registeredAt - b.registeredAt);
It sorts, yes. Oldest first. If you wanted the most recent first, it is backwards. The function runs, throws no error, and the list comes out sorted. It is just backwards.
The check: read what you asked for again and compare it sentence by sentence with what the code does. Not with what it says it does: with what it does.
2. The edge cases
An AI writes the happy path very well. The edges, less so, because those cases appear less often in what it learned from.
function average(numbers) {
const sum = numbers.reduce((a, b) => a + b, 0);
return sum / numbers.length;
}
Correct for [1, 2, 3]. With an empty list it returns NaN, which then travels
through your whole system and shows up on the user’s screen as “NaN” without
anything having failed along the way.
The four questions always worth asking:
- What happens if the list is empty?
- What happens if the value is
nullor was never sent? - What happens if the number is zero, or negative?
- What happens if the string is empty, or has accents and emoji in it?
3. Libraries that do not exist
This sounds odd until it happens to you. Sometimes the code imports a library with a very reasonable name that simply does not exist, or that exists but does not have that function.
import { formatRelativeDate } from 'date-fns'; // does not exist
It gets caught at install time or at run time, so it rarely gets far. But there is a dangerous version: if the invented name is plausible, somebody may have published a package under that exact name hoping you will install it. It is a known attack.
Before installing something an AI suggested, look at the package: how many downloads it has, when it was last published, whether the repository exists. Ten seconds.
4. Error handling that hides the error
This pattern shows up constantly:
try {
const data = await fetchData();
return data;
} catch (error) {
console.error(error);
return null;
}
It looks responsible. It is handling the error. But what it does is turn a
failure into a null that carries on through your program. Three functions
later something will break, and the error message will have nothing to do with
the real cause.
The right question at every catch: can this code genuinely continue without
that value? If the answer is no, let the failure propagate. A loud error in the
right place is worth more than a silent one in the wrong place.
5. What is not in the file you are looking at
An AI sees what you handed it. If you asked for a function in one file, it does not know that another file already has something almost identical, or that your project has an established way of doing things.
The result is silent duplication: two functions validating emails with slightly different rules, two date formats, two ways of calling the API.
Before accepting a new function, search your project for something similar. A
grep for the name of the concept is almost always enough.
6. The numbers and strings written by hand
if (user.plan === 'premium' && daysSinceSignup > 30) {
Where did the 30 come from? And is 'premium' exactly the string your database
uses, or does it say 'PREMIUM' over there?
When an AI does not know your values, it invents reasonable ones. Every number and every literal string in generated code is something to verify against your actual system.
The order I use
When the code is short, all of this takes a minute. When it is long, an order helps:
- Read what I asked for and compare it with what the code does.
- Find the literal values and verify each one.
- Mentally test with an empty list,
nulland zero. - Look at every
catchand ask whether it can continue without that value. - Check that the libraries exist.
- Check whether this was already in the project.
A way to make them show up on their own
Everything above is manual review. There is one thing that makes several of these problems surface without you looking for them: write the test before you ask for the code, even just one.
test('average of an empty list returns 0', () => {
expect(average([])).toBe(0);
});
That three-line test turns an ambiguous instruction into a verifiable criterion, and catches case 2 without you having to remember to look for it.
What does not change
The old rule still holds: do not ship code you cannot explain. If somebody asks why that line is there and the answer is “the AI gave it to me”, that code is not yours yet — and when it fails at three in the morning, you will not be able to fix it either.
Comments
Sign in to comment and to like this article.
No comments yet. Be the first to write one.