The 7 Classic Bugs in AI-Written Code (With Real Examples)
AI-generated code fails in repeatable patterns. Learn the seven classic bug shapes, from off-by-zero boundaries to swallowed errors, with runnable examples.
Here is the most useful fact about AI-generated bugs: they are not random. Language models produce code by pattern, and they produce mistakes by pattern too. The same handful of bug shapes shows up across every tool, every model, and every language, because they all share the same root causes: training data full of simplified examples, and generation that optimizes for plausible-looking code rather than verified behavior.
That is genuinely good news. Random bugs require luck to catch. Patterned bugs require a checklist. Learn these seven shapes and your eye will start snagging on them in any diff, which is most of what code review skill actually is.
Every example below is real, runnable behavior, not a hypothetical. Try them.
1. The boundary bug
AI code is disproportionately wrong at the edges: the first element, the last element, the zero case, the exact threshold. The training data is full of happy-path examples, so edges are where the model is guessing hardest.
A famous shape of this bug, in a function an assistant will cheerfully write for "get the last N items":
function getLastN(arr, n) {
return arr.slice(-n);
}
getLastN([1, 2, 3, 4, 5], 2); // [4, 5] correct
getLastN([1, 2, 3, 4, 5], 0); // [1, 2, 3, 4, 5] the entire array!
Why? Because -n is -0 when n is 0, and slice treats -0 exactly like 0, so the call becomes slice(0), which copies the whole array. Asking for zero items returns all of them. The function is correct for every input except one edge, and that edge is exactly the kind of thing that reaches production, because nobody manually tests "give me zero items."
Same family: > where the spec says >=, loops that miss the final element, ranges that include one item too many. The catch: for any generated function, mentally run it on zero, one, many, and the exact boundary value. Four inputs, ten seconds, catches an outsized share of everything.
2. The falsy-value trap
JavaScript and Python both have values that count as false in a condition (0, "", null/None; Python also treats empty lists and dicts as falsy, though JavaScript does not), and AI code constantly conflates "no value was provided" with "a falsy value was provided":
function paginate(items, pageSize) {
const size = pageSize || 10;
return items.slice(0, size);
}
paginate(rows); // size 10, fine
paginate(rows, 25); // size 25, fine
paginate(rows, 0); // size 10. A caller asking for zero gets ten.
0 || 10 evaluates to 10, so the legitimate value zero is silently overwritten by the default. The modern fix is ??, which only falls back on null/undefined: 0 ?? 10 is 0. Whether zero is even a valid page size is a spec question, which is the deeper point: the model never asked. The catch: every || default in a diff gets one question: "is a falsy value ever legitimate here?" Same for Python's or defaults and if not x: checks.
3. Happy-path blindness
Generated code is optimized for the demo. It handles the input shown in the prompt beautifully and ignores the inputs that arrive at 2 a.m. in production:
function averageScore(scores) {
const total = scores.reduce((sum, s) => sum + s, 0);
return total / scores.length;
}
averageScore([80, 90, 100]); // 90
averageScore([]); // NaN, which now silently flows downstream
An empty list produces NaN, and NaN does not crash; it propagates, corrupting every calculation it touches until something visibly breaks far from the cause. The same blindness applies to missing files, failed network calls, malformed JSON, and absent object fields. The model was not asked about failure, so failure was not handled. The catch: for every generated function ask "what happens when the input is empty, missing, or garbage?" If the answer is not visible in the code, the case is unhandled.
4. The hallucinated API
Sometimes the code calls a function that simply does not exist: array.flatten() instead of flat(), an invented keyword argument, a plausible method from some other library. Sometimes it invents an entire package, which has become a real security problem now that attackers register commonly hallucinated package names and fill them with malware.
This bug shape is big enough that we gave it its own article, covering why prediction-without-lookup makes it inevitable and the four flavors it comes in. The short version of the catch: any API you do not personally recognize gets thirty seconds against the real documentation, and any unfamiliar dependency gets checked on the registry before it is installed.
5. The stale pattern
Training data has a long memory, so generated code sometimes arrives wearing 2015's fashions: var instead of const, .substr() (long deprecated) instead of .substring() or .slice(), promise chains where your codebase uses async/await, Python's datetime.utcnow() (deprecated in favor of timezone-aware alternatives) in fresh new code.
Stale patterns are rarely bugs today. They are bugs on a delay: deprecated APIs eventually break on upgrade, and mixed eras of style make a codebase harder to maintain right now. They are also a tell. A diff full of dated idioms means the model is drawing on old training neighborhoods, which should lower your trust in everything else in that diff. The catch: know your language's current idiom, and when generated code uses a form you would not write, ask the tool for the modern equivalent and check what the deprecation notice says.
6. The swallowed error
AI tools know errors should be "handled," and the statistically most common handling in training data is, unfortunately, making the error disappear:
async function loadUserPrefs(userId) {
try {
const res = await fetch(`/api/prefs/${userId}`);
return await res.json();
} catch (err) {
return {};
}
}
Network down? Server on fire? JSON malformed? This function returns an empty object and tells no one. The application keeps running with silently wrong state, which is far more expensive to debug than a crash, because the failure is discovered miles downstream, stripped of its cause. Python's version is except: pass. The catch: every catch/except block in a generated diff must do something defensible with the error: rethrow it, log it with context, or return an explicit failure the caller checks. A catch block that converts failure into fake success is a bug with a delay timer.
7. The security hole
The most expensive shape. Training data is full of tutorial code, and tutorial code skips security for brevity, so generated code reproduces the classics with total confidence:
// SQL injection, straight from a thousand tutorials:
const rows = await db.query(
`SELECT * FROM users WHERE name = '${userInput}'`
);
// XSS, same origin story:
element.innerHTML = userComment;
String-built SQL hands your database to anyone who types a quote character; the fix is parameterized queries, always. Assigning untrusted text to innerHTML renders attacker-controlled markup, which can smuggle in script through vectors like image error handlers; the fix is textContent or proper sanitization. The same tutorial-inheritance produces hardcoded secrets, missing permission checks on the update endpoint, and passwords compared without hashing. The catch: any generated line where user-controlled data meets a database, HTML rendering, a file path, or a shell command deserves a paranoid second read. The model does not know your threat model. It only knows what tutorials look like.
Why this list is stable
Tools improve monthly, so why does the same list keep applying? Because each shape flows from something structural: simplified training examples (happy-path blindness, security holes, stale patterns), prediction without lookup (hallucinated APIs), and plausibility beating precision at the exact points where languages are tricky (boundaries, falsy values, error handling). Model upgrades lower the rates; they have not changed the shapes. A trained eye keeps paying off across tool generations, which is exactly why it is worth training.
Two habits turn this article into a skill:
- Review with the list. Next AI diff you receive, walk it once per shape: edges, falsy defaults, missing failure handling, unfamiliar APIs, dated idioms, silent catches, trust boundaries. Our full reviewing checklist wraps these into a complete workflow.
- Test the edges deliberately. Zero, one, many, weird. The four-input habit from bug #1 generalizes to almost everything.
Frequently asked questions
Does AI-generated code really have more bugs than human code?
It has different bugs, concentrated in these patterns, and it arrives faster and in higher volume, which is the real problem: more code per hour means more unreviewed edge cases per hour unless review scales with it. Human code has its own classic failure modes. What makes AI bugs distinctive is how systematically they cluster, which is also what makes them learnable.
Can I just ask the AI to check its code for these bugs?
As a first pass, yes, and it will catch some, especially if you name the specific patterns from this list in your request rather than asking "any bugs?" But the model reviewing its output shares the blind spots that produced the bugs, and it can confidently bless broken code. Use AI self-review as a filter, never as the verdict. The verdict is reading it yourself and running the edge cases.
Which of the seven is the most dangerous?
Ranked by cost per incident: security holes first, swallowed errors second, because both fail silently and surface late. Ranked by frequency: boundary bugs and happy-path blindness, which you will find in your very next session. The cheap insurance is the same for all of them: read the diff, question the defaults, and run the empty input.
Keep going
These seven shapes are the core of the failure-zoo module in our AI-Assisted Development course, where you practice catching each one in realistic AI-generated diffs, then take on a capstone reviewing an AI agent's work with planted defects. 61 interactive lessons, tool-agnostic, and the first three are free to preview without an account.
Kodion Team
Kodion Editorial
Written by the team that builds Kodion's courses: working engineers who test every example in the interactive editor before it ships. How we create and review content
Continue Learning
📚 Related Articles
Why AI Coding Tools Hallucinate APIs (and How to Catch It)
Why AI assistants invent functions, parameters, and whole packages that do not exist, the four flavors of code hallucination, and a workflow to catch them.
How to Review AI-Generated Code: A Checklist for Beginners
A practical 7-step checklist for reviewing AI-generated code: read the shape first, catch spec drift and scope creep, and verify before you trust.