What AI gets wrong, every time
1. The categories repeat
The model gets the same kinds of things wrong over and over. Once you've seen each category twice, you'll recognize them the third time before reading the code carefully. That recognition is most of the skill.
This unit names eight categories. Each one has a short TypeScript example. You're not memorizing the examples — you're tuning your pattern-matcher so the next time you see code from the model, your eyes go to the right place.
2. Off-by-one bugs
The model loves to write i <= arr.length when it meant i < arr.length. It loves to slice [start, end] when the API is [start, end). It loves to count "the first three" by stopping at index three instead of index two.
function processWindow(items: number[], windowSize: number) {
for (let i = 0; i <= items.length - windowSize; i++) {
const window = items.slice(i, i + windowSize);
process(window);
}
}The <= should be <. This passes unit tests with a window size of one. It breaks when the array is empty. Look at every comparison operator.
3. Hallucinated APIs
The model knows the shape of how libraries usually work, and will invent functions that should exist but don't.
const tomorrow = new Date().addDays(1);Date.prototype.addDays is not a JavaScript method. It's a method on some date libraries. The model conflated them. The code looks fine and fails at runtime. When a method on a standard library type "looks fine," check the docs.
4. Outdated patterns
The model was trained on years of code. Some of that code shows the old way of doing things — patterns that worked once, are deprecated now, and still compile.
class UserCard extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.userId !== this.props.userId) {
this.fetchUser(nextProps.userId);
}
}
}componentWillReceiveProps has been deprecated for years. The replacement is getDerivedStateFromProps, or hooks entirely. The model emits the old pattern because it saw it ten thousand times in training data. You catch this by knowing what the current idiom is in your stack.
5. Missing edge cases
The happy path is what the model writes. The unhappy paths are what it forgets.
function average(nums: number[]): number {
return nums.reduce((a, b) => a + b, 0) / nums.length;
}Empty array returns NaN. The function compiles, the types check, the happy-path test passes. The bug surfaces in production on the one day your upstream returns no data. Always ask: what about empty, null, very large, very small, negative, the boundary value, the duplicate, the unicode?
6. Security holes
The model writes string concatenation where you needed parameterized queries. It writes eval because the prompt said "dynamic." It logs the user's password while debugging.
async function findUser(name: string) {
const sql = `SELECT * FROM users WHERE name = '${name}'`;
return db.query(sql);
}Pass a name of '; DROP TABLE users; -- and watch the table go. The model wrote what looked simplest. Security is a thing you specify or the model omits.
7. Performance traps
The model picks the clearest algorithm, not the fastest. Often the same thing. Sometimes not.
function uniqueIds(records: { id: string }[]): string[] {
const out: string[] = [];
for (const r of records) {
if (!out.includes(r.id)) out.push(r.id);
}
return out;
}Array.includes is O(n). The whole function is O(n²). A Set makes it O(n). At a hundred records it's fine. At a hundred thousand, your endpoint times out.
8. Fragile error handling and over-engineering
Two more, briefly. Fragile error handling: the model wraps everything in a try/catch that swallows the error and returns null, hiding bugs until they cascade. Over-engineering: the model invents an abstract factory for a single case because abstract factories show up a lot in training data.
The cure for both is the same. After the model finishes, read the code and ask: did this need that? If the answer is no, delete it. Smaller code that does exactly the thing beats larger code that handles imaginary cases.
9. The skill is recognition
You don't catch these by reading carefully every time. You catch them by recognizing the shape. Off-by-one lives at comparison operators. Hallucinated APIs live at any unfamiliar method call. Security holes live where untrusted strings meet sensitive operations. The eye learns where to look.
After a few months of watching, you'll feel the wrongness before you can articulate it. That feeling is the skill arriving. Trust it, then go check.