Skills101

AI Coding Assistants · Lesson 3 of 8 · Updated ·

Reviewing AI-Written Code — A Checklist and 6 Real Bugs That Look Correct

AI-generated code is usually well-formatted, well-named and confidently commented — which makes bugs harder to spot, not easier. Reviewing it is now one of the most valuable skills a developer has. Here's how.

1. The review checklist

Code review checklist for AI output: does it solve the right problem, edge cases, correctness, security, fits the codebase, performance, tests, and do I understand every line

  1. Right problem? Re-read your requirement. Did it solve that — or an easier version?
  2. Edge cases: empty, zero, negative, very large, duplicates, missing fields, unicode, time zones, concurrency.
  3. Correctness details: off-by-one, integer vs float, rounding, == vs is, mutation of inputs.
  4. Errors: are exceptions handled sensibly — not swallowed, not crashing the whole request?
  5. Security: input validation, SQL/command injection, secrets, permissions (Web Security track).
  6. Fits the codebase: existing helpers reused? naming and patterns consistent? no surprise new dependency?
  7. Performance: nested loops over big data, N+1 database queries, loading everything into memory.
  8. Tests: do they exist, and do they test the edge cases — or only the happy path?
  9. Do I understand every line? If not, ask the AI to explain, or rewrite it until you do.

Now let's train your eye on six bugs that AI tools commonly produce because they're common in the code they learned from.

2. Bug 1: the mutable default argument

def add_score(score, scores=[]):      # looks harmless
    scores.append(score)
    return scores

print(add_score(90))   # student A
print(add_score(75))   # student B — should be [75]!
[90]
[90, 75]

The default list is created once and shared between calls, so student B inherits student A's score. Fix: scores=None then scores = [] if scores is None else scores.

3. Bug 2: money in floating point

price = 0.1 + 0.2
print(price == 0.3, price)

from decimal import Decimal
fee = Decimal("0.10") + Decimal("0.20")
print(fee == Decimal("0.30"), fee)

gst = round(Decimal("1999") * Decimal("0.18"), 2)
print("GST on ₹1999:", gst)
False 0.30000000000000004
True 0.30
GST on ₹1999: 359.82

Binary floats can't represent most decimals exactly. For money, use integer paise or Decimal — never float comparisons.

4. Bug 3: off-by-one in pagination

An AI-written page function, "20 items per page, pages start at 1":

items = list(range(1, 46))   # 45 courses

def page_buggy(n, size=20):
    return items[n * size:(n + 1) * size]

def page_fixed(n, size=20):
    return items[(n - 1) * size:n * size]

print("buggy page 1:", page_buggy(1)[:3], "... first item should be 1")
print("fixed page 1:", page_fixed(1)[:3])
print("fixed page 3:", page_fixed(3))
buggy page 1: [21, 22, 23] ... first item should be 1
fixed page 1: [1, 2, 3]
fixed page 3: [41, 42, 43, 44, 45]

The buggy version silently skips the first 20 items on page 1. No error, no crash — just missing data. These are the bugs users report weeks later.

5. Bug 4: timezone-naive dates

A contest that "closes at 9 PM" — but the server runs in UTC:

from datetime import datetime, timezone, timedelta

IST = timezone(timedelta(hours=5, minutes=30))
closes = datetime(2026, 11, 15, 21, 0, tzinfo=IST)         # 9 PM India time

server_now_naive = datetime(2026, 11, 15, 16, 0)            # what datetime.utcnow() might give: 4 PM, no zone
print("naive 4 PM UTC treated as local < 9 PM? ", server_now_naive < closes.replace(tzinfo=None))

real_now = datetime(2026, 11, 15, 16, 0, tzinfo=timezone.utc)   # same instant, with its zone
print("aware: is the contest still open?", real_now < closes)
print("9 PM IST in UTC is", closes.astimezone(timezone.utc).strftime("%H:%M"))
naive 4 PM UTC treated as local < 9 PM?  True
aware: is the contest still open? False
9 PM IST in UTC is 15:30

4 PM UTC is 9:30 PM in India, so the contest should already be closed. The naive comparison compared "16:00" with "21:00" as plain numbers and said it's still open — students could submit late. The timezone-aware comparison gets it right. Rule: store and compare times with time zones (usually in UTC), and convert to IST only for display.

6. Bug 5: the swallowed exception

def get_discount(code):
    try:
        return {"FEST50": 50, "EARLY20": 20}[code.upper()]
    except Exception:
        return 0            # hides EVERY error, including bugs

print(get_discount("fest50"))
print(get_discount(None))   # a real bug (None passed in) silently becomes "no discount"
50
0

except Exception: return default makes bugs invisible. Catch the specific exception you expect (KeyError here) and let real bugs surface (or log them).

7. Bug 6: quadratic performance

import time

registered = [f"user{i}@example.com" for i in range(20000)]
incoming = [f"user{i}@example.com" for i in range(19000, 21000)]

t = time.perf_counter()
dupes_slow = [e for e in incoming if e in registered]          # list search: O(n) each time
slow = time.perf_counter() - t

t = time.perf_counter()
registered_set = set(registered)
dupes_fast = [e for e in incoming if e in registered_set]      # set lookup: O(1) average
fast = time.perf_counter() - t

print(len(dupes_slow), len(dupes_fast), "duplicates found by both")
print("set version faster:", fast < slow)
1000 1000 duplicates found by both
set version faster: True

Fine on 20 test rows, painfully slow on 2 lakh real rows. Watch for in list inside loops and database queries inside loops (the N+1 problem).

8. How to review fast

  • Read the diff, not the chat. Review what will actually change in your repo (git diff).
  • Run it with the edge cases from section 1 before reading deeply — failures point you to the bug.
  • Ask the AI to review its own code against your checklist ("list edge cases this doesn't handle") — it often finds real problems, but you decide.
  • Keep changes small so reviews stay possible.

Common mistakes

  • Trusting code because it's well-commented.
  • Reviewing only the happy path.
  • Accepting a new dependency without checking it exists, is maintained and is needed.
  • Merging AI code you can't explain in an interview.

Quick recap

  • Use the checklist: right problem, edge cases, correctness, errors, security, fit, performance, tests, understanding.
  • Classic AI-plausible bugs: mutable defaults, float money, off-by-one, naive datetimes, swallowed exceptions, O(n²) loops.
  • Run edge cases first; review the diff; keep changes small; you own the result.

Practice what you learned

📝 Quiz: Reviewing AI Code5 questions · instant answers & explanations · +10 XP · counts towards your free certificateTake the quiz →