# Before You Merge AI-Generated Code, Ask These 12 Questions

> Source: <https://dev.to/codzee_io/before-you-merge-ai-generated-code-ask-these-12-questions-14p3>
> Published: 2026-08-14 12:21:07+00:00

I've merged plenty of AI-generated code that was genuinely fine. I've also caught myself almost merging code that looked fine and wasn't, because it read like something a competent person wrote and my brain filled in the rest.

Over the last year I've settled into a rough set of questions I run through before approving anything I didn't write line by line myself, generated or not. Here they are, in the order I actually ask them.

It's easy to review whether code works and skip whether it solves the right thing. AI tends to answer the literal prompt, not the intent behind it.

``` python
def get_active_users():
    return db.query("SELECT * FROM users WHERE active = true")
```

If "active" was supposed to mean "logged in within 30 days" and not a boolean flag that's rarely updated, this passes every test and still solves the wrong problem.

**Reviewer tip:** Read the original ticket or request before reading the diff. Check the code against the intent, not just the literal ask.

Not "does it look reasonable," actually understand it, line by line, well enough to explain it to someone else.

**Reviewer tip:** Try to explain the function out loud in one sentence per major step. If you get stuck anywhere, that's the part you haven't actually reviewed yet, just skimmed.

Every implementation bakes in assumptions about the shape of the data, the order things happen in, or what "normal" looks like.

```
function getLatestOrder(orders) {
  return orders[orders.length - 1];
}
```

This assumes `orders`

is sorted chronologically and never empty. Neither assumption is stated anywhere.

**Reviewer tip:** Ask "what does this assume about its inputs that isn't checked anywhere?" Write the answer down, literally, in the PR comment if it matters.

Bad input isn't an edge case, it's a certainty over a long enough timeline.

``` python
def parse_age(value):
    return int(value)
```

Pass it `"25"`

and it works. Pass it `"twenty-five"`

, `None`

, or `-5`

and you get a crash or a nonsensical value with no complaint.

**Reviewer tip:** Pick three inputs that would never appear in a demo but could plausibly appear in production: empty, wrong type, absurdly large. Trace through what actually happens.

Generated code frequently assumes the network, database, or third-party API always responds successfully.

``` js
async function getExchangeRate(currency) {
  const response = await fetch(`https://api.example.com/rates/${currency}`);
  const data = await response.json();
  return data.rate;
}
```

No timeout, no handling for a non-200 response, no fallback. If that API is slow or down, this fails in whatever way `fetch`

and `.json()`

happen to fail, which may not be a clear error at all.

**Reviewer tip:** For every external call, ask "what does the caller see if this times out or returns an error status?" If the answer is "an unhandled exception," that's worth a comment.

There's a difference between "there's an auth check" and "it's the right auth check."

``` js
if (!req.user) return res.status(401).send('Unauthorized');
const doc = await db.documents.findById(req.params.id);
res.json(doc);
```

This confirms someone is logged in. It never confirms they own or have access to *this* document.

**Reviewer tip:** For any endpoint touching a specific resource, ask "does this check the resource belongs to the requester, or just that the requester is logged in?"

Look at what actually goes into logs, error responses, and API payloads, not just what the happy path returns.

```
catch (err) {
  res.status(500).json({ error: err.message, stack: err.stack });
}
```

Fine in local development. In production this can leak file paths, query fragments, or internal structure to whoever triggers the error.

**Reviewer tip:** Grep the diff for `console.log`

, `print`

, and catch blocks. Check what they actually expose.

Generated code sometimes over-engineers a simple problem with extra configuration, unnecessary abstraction layers, or generic solutions to specific problems.

**Reviewer tip:** Ask "could this be half the length and still be correct?" If yes, that's worth pushing back on, complexity has an ongoing cost even when it's not technically wrong.

Generated tests often confirm the code does what it does, not that it does what it should.

``` python
def test_apply_discount():
    assert apply_discount(100, 10) == 90
```

This confirms the arithmetic. It says nothing about a discount over 100%, a negative price, or invalid input.

**Reviewer tip:** For each test, ask "what wrong implementation would still pass this?" If you can think of one easily, the test isn't pinning down enough.

Locally correct code can still be a long-term problem if it introduces a new pattern the codebase doesn't already use, a different error-handling style, a new HTTP client, a different logging approach.

**Reviewer tip:** Before approving, check one comparable file elsewhere in the codebase. If the patterns don't match, ask whether that's intentional.

Code that's correct for one request at a time can break under concurrent access, especially anything involving shared state or caching.

``` python
counter = {}

def increment(key):
    counter[key] = counter.get(key, 0) + 1
```

Fine single-threaded. Under concurrent requests this can lose increments, since read-then-write isn't atomic.

**Reviewer tip:** For anything touching shared state, ask "what happens if this runs twice at the exact same moment?"

The final check. If someone opens this file in eight months with zero memory of this PR, can they figure out what it does and why from the code and comments alone?

**Reviewer tip:** Read the diff as a stranger would, not as someone who already knows what it's supposed to do. If it doesn't hold up, add a comment now while the reasoning is still fresh.

```
## AI-Generated Code Review Checklist

- [ ] Solves the actual requirement, not just a literal reading of it
- [ ] I understand every part of this well enough to explain it
- [ ] Assumptions are identified and actually hold true
- [ ] Handles empty, invalid, and unexpected input
- [ ] Handles external service failures and timeouts
- [ ] Permissions checked against the specific resource, not just login state
- [ ] No sensitive data leaked via logs, errors, or responses
- [ ] No unnecessary complexity for the problem size
- [ ] Tests check behavior, not just current output
- [ ] Matches existing codebase conventions
- [ ] Considered behavior under concurrent access or load
- [ ] Understandable to a developer with no context on this PR
```

None of this is exotic review practice. It's the same discipline good engineers apply to any code they didn't personally trace through, generated or not. The only thing that's changed is how often that situation comes up, and how easy it is to skip these questions when the first draft already looks like someone competent wrote it.
