How Do You Review AI-Generated Code? A Line-by-Line Checklist for Developers
Problem
Last week I shipped a bug to production. The code came from Claude, and I had reviewed it—or so I thought. I looked at the output, saw the tests pass, and merged it. Two hours later, users started reporting data corruption.
The fix took three hours. The root cause took longer to understand: I had treated AI-generated code as production-ready when it was actually a first draft.
Here’s what the AI gave me:
async function updateUser(userId, updates) { const user = await User.findById(userId); Object.assign(user, updates); return await user.save();}Looks fine, right? That’s what I thought. But it has three problems:
- No validation of
updatesobject—malicious fields could be injected - No error handling—database errors crash the app
- No transaction—partial updates on failure corrupt data
I missed all three because I reviewed it like code from a senior developer, not like code from an AI that makes plausible but incomplete solutions.
What happened?
I searched for best practices on reviewing AI code and found a Reddit thread that described the exact problem I faced. Developers are generating code 3x to 5x faster than they can review it, creating a massive quality deficit.
The key insight: AI output is a first draft, not production code.
When I looked back at my review process, I saw the problem:
- I scanned for obvious bugs, not for missing logic
- I trusted variable names without checking their scope
- I assumed edge cases were handled because the code “looked complete”
- I treated passing tests as proof of correctness
This is exactly what the Reddit thread warned about:
“No matter how good the prompt is, you still have to go through the generated code line by line”
And more importantly:
“For anything I ship to a production environment, I make sure I go through the entire generated codebase and hone the logic, fix issues, and take back ownership of it”
How I review AI-generated code now
I developed a checklist that I run through for every piece of AI-generated code before it reaches my codebase.
Checklist: Before You Commit AI Code
[ ] Have I read every line, not just scanned it?[ ] Do I understand why each line exists?[ ] Have I checked all variable scopes and lifetimes?[ ] Have I verified error handling for every external call?[ ] Have I considered edge cases the AI might have missed?[ ] Does the code match my project's patterns and style?[ ] Have I manually refactored at least some of it?[ ] Can I explain this code to a teammate without looking at it?If I can’t check all boxes, I don’t commit.
The line-by-line review process
I now treat AI code like code from a junior developer—competent but needs supervision.
Step 1: Read every line
I literally read each line out loud in my head. This forces me to slow down and actually process the code, not just recognize patterns.
Here’s how I reviewed a recent AI-generated function:
async function processOrder(orderId) { // Line 1: What if orderId is null? undefined? string? const order = await Order.findById(orderId);
// Line 2: What if items is empty? What if product lookup fails? const products = await Promise.all( order.items.map(item => Product.findById(item.productId)) );
// Line 3: What about quantities? Discounts? Currency? const total = products.reduce((sum, p) => sum + p.price, 0);
// Line 4: What if payment fails? Do we retry? Log? await PaymentService.charge(order.userId, total);
// Line 5: What if this fails after payment succeeded? order.status = 'paid'; await order.save();
return order;}Each line triggered questions the AI didn’t answer.
Step 2: Refactor to match your style
The Reddit thread had great advice:
“I let the AI write the messy first draft, and then I go in and manually refactor every single line to make it mine”
This is crucial. Manual refactoring forces you to understand every line.
Here’s my refactored version:
async function processOrder(orderId) { // Validate input if (!orderId) { throw new ValidationError('orderId is required'); }
const order = await Order.findById(orderId); if (!order) { throw new NotFoundError(`Order not found: ${orderId}`); }
if (order.items.length === 0) { throw new ValidationError('Order has no items'); }
// Fetch products with error handling const products = await Promise.all( order.items.map(async (item) => { const product = await Product.findById(item.productId); if (!product) { throw new NotFoundError(`Product not found: ${item.productId}`); } return { product, quantity: item.quantity }; }) );
// Calculate total with quantities const total = products.reduce( (sum, { product, quantity }) => sum + (product.price * quantity), 0 );
// Process payment with transaction const transaction = await PaymentService.charge(order.userId, total); if (!transaction.success) { throw new PaymentError(`Payment failed: ${transaction.error}`); }
// Update order atomically order.status = 'paid'; order.paymentId = transaction.id; await order.save();
return order;}This version:
- Validates all inputs
- Handles missing data
- Calculates with quantities (the AI missed this)
- Captures payment ID for audit
- Has clear error types
Step 3: Take ownership
The Reddit thread emphasized:
“I do something similar where I let Claude handle the boilerplate but force myself to write the core logic from scratch”
This is the ownership step. Even if the AI code is correct, rewriting the core logic yourself ensures you actually understand it.
For the order processing, I wrote the error handling and validation from scratch. The AI gave me the structure, but I owned the logic.
Common AI code patterns to watch for
After reviewing hundreds of AI-generated code blocks, I’ve identified patterns that almost always need attention.
Pattern 1: Missing null checks
AI often assumes data exists:
const user = await User.findById(userId);return user.name; // Crashes if user is nullconst user = await User.findById(userId);if (!user) { throw new NotFoundError(`User not found: ${userId}`);}return user.name;Pattern 2: Shallow error handling
AI catches errors but doesn’t handle them well:
try { await sendEmail(user.email, content);} catch (e) { console.log(e); // Logged but ignored}try { await sendEmail(user.email, content);} catch (e) { logger.error('Email send failed', { userId: user.id, error: e.message }); // Queue for retry await EmailQueue.add({ userId: user.id, content, attempt: 1 });}Pattern 3: Missing transactions
AI generates sequential operations without considering atomicity:
await account.debit(amount);await ledger.record(account.id, amount, 'debit'); // Fails? Money goneawait db.transaction(async (trx) => { await account.debit(amount, { transaction: trx }); await ledger.record(account.id, amount, 'debit', { transaction: trx });});Pattern 4: Ignored edge cases
AI handles the happy path well but misses edge cases:
function formatName(firstName, lastName) { return `${firstName} ${lastName}`; // What about middle names? Suffixes?}function formatName(firstName, lastName, options = {}) { const parts = [firstName, options.middleName, lastName, options.suffix] .filter(Boolean); return parts.join(' ');}A practical workflow
Here’s my current workflow for AI-generated code:
1. Generate code with AI2. Read every line (out loud if needed)3. Run through checklist4. Identify patterns that need attention5. Manually refactor core logic6. Write additional tests for edge cases7. Review again after tests pass8. Commit only when I can explain every lineThe time investment is significant. A 20-line function might take 5 minutes to generate but 15 minutes to review properly. That’s not a bug—it’s the cost of quality.
Why this matters
The Reddit thread had a sobering observation:
“We’re currently creating code 3x to 5x faster than we can actually review it, creating a massive quality deficit”
This deficit compounds. Every line of unaudited AI code is technical debt you’ll pay for later—usually at 3 AM when production is down.
The developers who thrive with AI aren’t the ones who generate code fastest. They’re the ones who review most carefully. They treat AI as an accelerator for the easy stuff (boilerplate, structure, patterns) but maintain strict standards for the hard stuff (logic, edge cases, ownership).
Summary
In this post, I showed how to properly review AI-generated code using a line-by-line checklist. The key insight is that AI output is a first draft that requires human refinement, not production-ready code. You must read every line, understand the logic completely, refactor to match your style, and take ownership before committing.
The checklist approach catches what visual scanning misses: missing null checks, shallow error handling, absent transactions, and ignored edge cases. The extra time spent on review is less than the time spent debugging production issues from unaudited code.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments