Common Patterns in Broken AI Code
After analyzing thousands of lines of AI-generated code, patterns emerge. The same mistakes repeat across projects, languages, and use cases.
Recognizing these patterns is the first step to catching them. Here are the most common ways AI code breaks—and how to fix it.
Pattern 1: The Trusting Input Pattern
AI code often assumes inputs are well-formed and safe. They're not.
The Problem
// AI generated - assumes userId is always valid
async function getUser(userId) {
return db.query(`SELECT * FROM users WHERE id = ${userId}`);
}
This code works perfectly when userId is "123". But what if it's "123 OR 1=1"?
Why AI Does This
AI models are trained on example code, not production code. Examples often skip validation for clarity. AI learns that pattern and reproduces it.
The Fix
Always validate and sanitize inputs:
async function getUser(userId) {
if (!/^\d+$/.test(userId)) {
throw new Error('Invalid user ID');
}
return db.query('SELECT * FROM users WHERE id = ?', [userId]);
}
Red Flags to Watch For
- String concatenation in queries
- Direct use of request parameters
- Missing type checks
- No validation functions
Pattern 2: The Incomplete Error Pattern
AI generates try-catch blocks, but the catches are often useless.
The Problem
// AI generated - catches and swallows
async function processPayment(paymentData) {
try {
const result = await paymentGateway.charge(paymentData);
return result;
} catch (error) {
console.log(error);
}
}
This catches errors but doesn't handle them. The caller gets undefined instead of knowing the payment failed.
Why AI Does This
AI sees many examples with console.log(error) and learns this as "how to handle errors." It doesn't understand that production code needs proper error handling.
The Fix
Handle errors meaningfully:
async function processPayment(paymentData) {
try {
const result = await paymentGateway.charge(paymentData);
return { success: true, result };
} catch (error) {
logger.error('Payment failed', { error, paymentData: paymentData.id });
return { success: false, error: error.message };
}
}
Red Flags to Watch For
- Empty catch blocks
console.login catch blocks- No error propagation
- No user feedback
Pattern 3: The Implicit State Pattern
AI code often modifies state without being explicit about it.
The Problem
// AI generated - mutates global state
let currentUser = null;
function setUserData(data) {
currentUser = data;
processQueue();
}
This function mutates currentUser as a side effect. Other code might not expect this.
Why AI Does This
AI focuses on the immediate task—"process user data"—without considering the broader implications of global state mutations.
The Fix
Make state changes explicit:
function setUserData(data) {
// Return new state instead of mutating
return {
user: data,
shouldProcessQueue: true
};
}
// Or use a state manager with explicit updates
stateManager.update('user', data);
Red Flags to Watch For
- Global variables being modified
- No return value but changes occur
- Side effects in "getter" functions
- Missing state documentation
Pattern 4: The Happy Path Only Pattern
AI generates code that works when everything goes right. But what about when it doesn't?
The Problem
// AI generated - no edge cases
function calculateDiscount(price, tier) {
if (tier === 'premium') return price * 0.8;
if (tier === 'basic') return price * 0.9;
return price;
}
What if tier is null? Or 'PREMIUM'? Or an object?
Why AI Does This
AI models optimize for the common case in their training data. Edge cases are rare in examples, so AI doesn't learn to handle them.
The Fix
Handle all cases explicitly:
function calculateDiscount(price, tier) {
if (typeof price !== 'number' || price < 0) {
throw new Error('Invalid price');
}
const normalizedTier = (tier || '').toLowerCase();
const discounts = {
premium: 0.8,
basic: 0.9,
free: 1.0
};
const discount = discounts[normalizedTier] ?? 1.0;
return price * discount;
}
Red Flags to Watch For
- No null checks
- No type validation
- Missing else branches
- No default cases in switches
Pattern 5: The Overspecified Pattern
Sometimes AI code is too specific to work in real conditions.
The Problem
// AI generated - too specific
function parseApiResponse(response) {
return response.data.users[0].profile.name;
}
If the API returns an empty users array, this crashes with Cannot read property 'profile' of undefined.
Why AI Does This
AI training data often shows ideal responses. The model learns that APIs always return the expected structure.
The Fix
Handle variability:
function parseApiResponse(response) {
return response?.data?.users?.[0]?.profile?.name ?? 'Unknown';
}
Red Flags to Watch For
- Deep property access without checks
- Assuming array indices exist
- No null coalescing
- No fallback values
Pattern 6: The Security Theater Pattern
AI sometimes generates security code that looks correct but isn't.
The Problem
// AI generated - looks secure, isn't
function hashPassword(password) {
return btoa(password); // Base64 encoding, not hashing!
}
// Or even worse
function checkAuth(token) {
return token.startsWith('admin_');
}
Base64 is encoding, not hashing. Token prefix checking is trivial to bypass.
Why AI Does This
AI doesn't understand cryptography. It sees patterns like "transform password" and applies whatever transformation it knows.
The Fix
Use proper security libraries:
import bcrypt from 'bcrypt';
async function hashPassword(password) {
return bcrypt.hash(password, 10);
}
async function verifyPassword(password, hash) {
return bcrypt.compare(password, hash);
}
Red Flags to Watch For
- Custom encryption/hashing
- Weak algorithms (MD5, SHA1)
- Security checks that can be spoofed
- Hardcoded keys or salts
Pattern 7: The Async/Await Without Error Handling Pattern
Modern AI uses async/await, but often forgets error handling.
The Problem
// AI generated - unhandled promise rejection
async function fetchUserData(userId) {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
return data;
}
If the fetch fails, this throws an unhandled promise rejection.
Why AI Does This
Many examples show async/await without try-catch for brevity. AI learns this pattern and doesn't distinguish between example code and production code.
The Fix
Always handle async errors:
async function fetchUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
logger.error('Failed to fetch user', { userId, error });
throw new UserFetchError(userId, error);
}
}
Red Flags to Watch For
- Async functions without try-catch
- Awaiting without checking response status
- No error context
- Silent failures
Pattern 8: The Premature Optimization Pattern
AI sometimes optimizes for the wrong things.
The Problem
// AI generated - caching without invalidation
const cache = {};
function getUser(userId) {
if (cache[userId]) {
return cache[userId];
}
const user = db.query(`SELECT * FROM users WHERE id = ${userId}`);
cache[userId] = user;
return user;
}
This cache never invalidates. If the user updates their profile, they'll see stale data forever.
Why AI Does This
AI sees caching examples and applies the pattern without understanding cache invalidation—one of the hardest problems in CS.
The Fix
Include cache invalidation or use a proper cache:
const cache = new Map();
function getUser(userId) {
const cached = cache.get(userId);
if (cached && Date.now() - cached.timestamp < 60000) {
return cached.data;
}
const user = db.query('SELECT * FROM users WHERE id = ?', [userId]);
cache.set(userId, { data: user, timestamp: Date.now() });
return user;
}
Red Flags to Watch For
- Caching without TTL
- No cache invalidation logic
- Memory leaks in caches
- Stale data issues
How to Spot These Patterns
Code Review Checklist
When reviewing AI-generated code, look for:
- Input handling - Is all input validated?
- Error handling - Are errors properly caught and handled?
- State management - Are state changes explicit?
- Edge cases - What happens with unexpected input?
- Security - Is security real or theater?
- Async - Are promises handled?
- Caching - Is there invalidation?
Automated Detection
Use static analysis tools to catch patterns:
# ESLint rules for common issues
npx eslint --rule 'no-implicit-globals: error' \
--rule 'no-unsafe-optional-chaining: error' \
--rule 'require-atomic-updates: error'
The Fix-First Mindset
When you spot these patterns, don't just fix the immediate issue. Ask:
- Why did AI generate this? - Understanding helps you catch similar issues
- What else might be affected? - Patterns tend to repeat
- How can I prevent this? - Add lints, tests, or prompts
Moving Forward
AI coding assistants are powerful tools. They're not perfect, but recognizing their failure patterns makes you a better developer.
The goal isn't to stop using AI—it's to use it wisely. Know what to look for, fix what's broken, and ship better code.
Want automated detection of these patterns? VCX scans AI-generated code for common failure patterns.