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
- Right problem? Re-read your requirement. Did it solve that — or an easier version?
- Edge cases: empty, zero, negative, very large, duplicates, missing fields, unicode, time zones, concurrency.
- Correctness details: off-by-one, integer vs float, rounding,
==vsis, mutation of inputs. - Errors: are exceptions handled sensibly — not swallowed, not crashing the whole request?
- Security: input validation, SQL/command injection, secrets, permissions (Web Security track).
- Fits the codebase: existing helpers reused? naming and patterns consistent? no surprise new dependency?
- Performance: nested loops over big data, N+1 database queries, loading everything into memory.
- Tests: do they exist, and do they test the edge cases — or only the happy path?
- 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.