Lesson 5: Code Review and Quality
Course: Coding with AI Assistants
Estimated time: 25 minutes
Difficulty: Intermediate Builder
Here's a scenario you've either lived or will live soon: you ship an AI-generated feature. It works in testing. Two days later, a user hits an edge case you didn't think of. Or a security researcher emails you about an input validation gap. Or your database query is loading 10,000 rows when it should load 10.
AI writes fast code. It doesn't write careful code by default. The difference between fast and careful is you - the reviewer.
This lesson gives you a systematic way to review AI-generated code so you catch the things AI misses.
What AI Gets Wrong (Consistently)
AI coding tools are better at some things than others. Knowing the failure patterns means you know where to look hardest.
Security - the most dangerous gap
AI-generated code handles authentication and authorization approximately. "Approximately" in security means vulnerable.
Common AI security mistakes:
- Forgetting to verify the authenticated user owns the resource they're requesting. The AI protects the route with auth middleware but doesn't check that
user.id === resource.owner_idinside the handler. - Input going directly into a database query without sanitization (SQL injection risk in raw queries, though ORMs help here).
- API keys or sensitive values appearing in client-side code or logs.
- Missing rate limiting on endpoints that trigger expensive operations or can be used for enumeration.
- Auth checks that can be bypassed by manipulating URL parameters.
Performance - the common slow burn
AI generates code that works correctly at small scale and degrades badly at real scale.
Common AI performance mistakes:
- N+1 queries. The AI fetches a list of items, then fetches related data for each item in a loop. This is the classic ORM trap and AI falls into it constantly.
- Missing pagination. The AI returns all records when you needed it to return the first 20.
- No caching for expensive operations. Every request re-runs a slow database query or an expensive AI API call.
- Unnecessary re-renders in React. State updates that trigger full re-renders of large component trees.
Edge cases - the silent failures
AI tests the happy path in its head and writes code for that. Real users aren't on the happy path.
Common AI edge case misses:
- Empty state. What happens when the list has zero items? The AI usually handles this, but not always.
- Concurrent requests. Two users submit the same form at the same time. Race conditions that only show up under load.
- Network failures mid-operation. The payment went through but the database update failed. Now the user paid but has no access.
- Very long inputs. The AI assumes inputs are a reasonable length. Users paste entire documents into fields meant for short text.
Readability - the slow maintainability tax
AI-generated code tends toward verbosity and can produce abstractions that work but are hard to understand later.
This is lower urgency than security and correctness, but matters if you're working in the codebase for more than a week.
The Review Checklist
Go through this for every significant block of AI-generated code before you merge or deploy it.
Security
- [ ] Does every database query verify the user has permission to access that data?
- [ ] Is any user input going into a query or command without sanitization?
- [ ] Are there any API keys, tokens, or sensitive values in client-side code?
- [ ] Does the endpoint need rate limiting and does it have it?
- [ ] If there's an auth check, can it be bypassed?
Correctness
- [ ] Does the code do what the spec said?
- [ ] What happens when any input is null or undefined?
- [ ] What happens when an external service (database, API) returns an error?
- [ ] What happens when the input is valid but at the extreme end (empty string, very large number, very long string)?
- [ ] Is the returned data in the shape the caller expects?
Performance
- [ ] Are there any database queries inside loops?
- [ ] Does anything return an unbounded list (no limit or pagination)?
- [ ] Is anything expensive being computed on every request that could be cached?
- [ ] For React components: does state update trigger more re-renders than needed?
Readability
- [ ] Can you read this in 6 months and understand what it does?
- [ ] Are variable and function names descriptive?
- [ ] Is there any logic that needs a comment to explain why, not just what?
You don't need to check every item on every function. Use judgment. A one-line utility function doesn't need a full security audit. A payment handler does.
Using AI to Review AI-Generated Code
Yes, this works. And yes, it can catch things you miss.
The technique: after generating code, open a fresh context (new session or clear the conversation) and paste the code back in with a review request. The fresh context means the AI reviews what's actually there, not what it intended to write.
Effective review prompts:
Security review:
Review this code for security issues. Focus on:
- Authorization: does it verify the user has permission to access this resource?
- Input validation: is any user input handled unsafely?
- Data exposure: is any sensitive data leaked in responses or logs?
- Rate limiting: could this endpoint be abused?
Code:
[paste code]
Performance review:
Review this code for performance problems. Look for:
- N+1 query patterns
- Missing pagination or limits on queries
- Expensive operations that run on every request but could be cached
- Unnecessary re-computation
Code:
[paste code]
Edge case review:
What edge cases does this code NOT handle? Think about:
- Empty inputs and null values
- Concurrent requests
- Partial failures (one operation succeeds, a dependent one fails)
- Inputs at the extremes (very large, very small, very long)
Code:
[paste code]
The AI will catch some things and miss others. Its review is a starting point, not a complete answer. But it consistently finds things that non-obvious on first read, especially N+1 patterns and authorization gaps.
Testing Strategies for AI-Assisted Development
Testing AI-generated code is different from testing code you wrote yourself. When you write code, you have an intuition for where it might fail. With AI-generated code, you're starting with less intuition and more surface area.
Write tests for the edge cases AI tends to miss
The happy path usually works. Write tests that cover:
- The empty case (empty list, null user, zero amount)
- The boundary case (exactly at the limit, one over the limit)
- The error case (external service fails, invalid input)
- The concurrent case (two requests at the same time, if relevant)
AI is good at generating these tests once you tell it which cases to cover. The skill is knowing which cases to ask for.
Use AI to generate comprehensive test cases
Give it the function and ask it to list every interesting input combination, then write tests for them.
Here's a function that processes a payment amount.
[paste function]
List every interesting input you can think of that might cause a different code path or failure. Then write tests for each one using Jest.
This workflow often surfaces edge cases you wouldn't have written tests for. It's one of the highest-value uses of AI in testing.
Integration tests over unit tests for AI code
AI-generated code often has the logic distributed across multiple functions that call each other. Unit testing each function in isolation misses the integration failures. A test that goes from the API request through to the database response is more likely to catch real bugs.
For Next.js API routes, test the whole route handler, not just the database query inside it. For React components, test the user interaction, not just the component rendering.
Test the AI's test code
When you ask AI to write tests, review them. AI writes tests that pass, sometimes by testing the wrong thing. Common AI test anti-patterns:
- Testing implementation details instead of behavior (if the function changes how it works internally but the output is still correct, the test shouldn't break)
- Mocking too aggressively (the test doesn't actually exercise the code path you care about)
- Testing that no error is thrown instead of testing the actual return value
Run the tests, yes. But also read them and ask: "is this test actually checking what I think it's checking?"
A Practical Quality Workflow
Here's how this fits into a real development workflow without adding hours of overhead.
For small changes (single function, single route):
- Generate the code
- Skim the security section of the checklist (2 minutes)
- Run any existing tests
- Test manually in your dev environment
- Ship
For medium changes (new feature, multiple files):
- Generate the code
- Run the AI security review prompt on the main handler/route (5 minutes)
- Walk through the correctness checklist (5 minutes)
- Write tests for the 2-3 most likely edge cases
- Test manually end-to-end
- Ship
For large or sensitive changes (payment flows, auth, data access patterns):
- Generate the code
- Full checklist review
- AI security review with fresh context
- Write tests for all edge cases you can think of
- Ask one other person to look at the auth/security parts
- Manual end-to-end testing including error scenarios
- Ship
The point is proportionality. Not every function needs a security audit. The payment webhook handler does. Match the rigor to the risk.
The Review That Actually Matters: Does It Do What Users Need?
Technical correctness is necessary but not sufficient. The best-reviewed, best-tested, most secure code is still wrong if it doesn't do what users actually need.
After you've reviewed the code for quality, ask one more question: does this actually solve the problem a user would bring to it?
This means testing with realistic data, not test@example.com and "test project". Paste in a real brief you might give this tool. Run the feature as if you're a first-time user who's never seen it before. Try to do something slightly different from what you designed for and see what breaks.
This is user testing, not code review. It's a different skill and it catches different problems. The combination of both is how you ship things that are technically solid and actually useful.
Exercise: Review and Improve 3 AI-Generated Functions
Find 3 functions in a project you've been building with AI assistance. They could be API handlers, utility functions, React hooks, whatever you have.
For each function:
Step 1: Skim review (5 minutes)
Go through the security and correctness checklist items that apply to this function. Note anything that looks off.
Step 2: AI review (5 minutes)
Paste the function into a fresh AI session. Use the security review prompt or the edge case review prompt, depending on what the function does. Read the AI's output. Add any new issues to your list.
Step 3: Write two tests (10 minutes)
Write one test for the happy path and one test for the edge case you think is most likely to fail in production. Run them. If the edge case test fails, fix the function.
After you've done all three functions, answer these questions:
- What types of problems did you find most often?
- Was the AI review useful or did it mostly surface things you'd already caught?
- Which function had the most significant gap between "AI generated it" and "it's production-ready"?
The answers will shape your review instincts for the next project.
Key Takeaway
AI writes fast code that covers the happy path. Your job as the reviewer is to find the security gaps, the edge cases, and the performance problems that AI consistently misses - and fix them before users do.
What's Next
You've completed Course 2. You have a working mental model for AI-assisted development, a tool choice, a prompting system, a strategy for large projects, and a review workflow. The next step is putting it all together on a real project. If you haven't worked through Course 1 yet (Ship Your First AI App), that's where the rubber meets the road.