When NOT to Use AI
Recognize scenarios where traditional development approaches are more appropriate than AI assistance.
When NOT to Use AI: Pitfalls and Anti-Patterns
You've probably heard the hype: AI coding assistants can write entire applications, debug complex issues, and make you 10x more productive. While AI tools like GitHub Copilot, Cursor, and ChatGPT are genuinely powerful, they're not magic wands. In fact, misusing them can slow you down, introduce bugs, and prevent you from growing as a developer.
Let's explore the situations where AI assistance becomes a liability rather than an asset, and learn to recognize these anti-patterns before they bite you.
The "Copy-Paste Without Understanding" Trap
This is the most common—and most dangerous—mistake developers make with AI tools.
The Problem
When AI generates code that "just works," it's tempting to paste it directly into your project and move on. But code you don't understand is a ticking time bomb.
# AI suggested this for handling user authentication
@app.route('/login', methods=['POST'])
def login():
user = User.query.filter_by(username=request.form['username']).first()
if user and check_password_hash(user.password, request.form['password']):
session['user_id'] = user.id
return redirect(url_for('dashboard'))
return render_template('login.html', error='Invalid credentials')
Looks reasonable, right? But can you spot the issues?
- No CSRF protection
- Vulnerable to timing attacks
- No rate limiting (brute force risk)
- Session fixation vulnerability
- No input validation
The Fix
Always treat AI-generated code as a starting point, not a finished product. Here's a better approach:
# After understanding and improving the AI suggestion
from flask_wtf.csrf import CSRFProtect
from flask_limiter import Limiter
import secrets
@app.route('/login', methods=['POST'])
@limiter.limit("5 per minute") # Rate limiting
def login():
# CSRF protection handled by Flask-WTF
username = request.form.get('username', '').strip()
password = request.form.get('password', '')
# Input validation
if not username or not password:
return render_template('login.html', error='All fields required')
user = User.query.filter_by(username=username).first()
# Constant-time comparison to prevent timing attacks
if user and secrets.compare_digest(
user.password.encode('utf-8'),
check_password_hash(user.password, password).encode('utf-8')
):
# Regenerate session to prevent fixation
session.regenerate()
session['user_id'] = user.id
return redirect(url_for('dashboard'))
return render_template('login.html', error='Invalid credentials')
Action Step: Before accepting any AI suggestion, ask yourself: "Can I explain what each line does and why it's necessary?"
Learning Fundamentals: When AI Actually Hurts
AI can become a crutch that prevents you from developing essential skills.
Situations Where You Should Struggle
1. Learning New Concepts
If you're learning recursion, SQL joins, or async programming for the first time, resist the urge to ask AI for solutions immediately. The struggle is where learning happens.
// DON'T immediately ask AI: "Write a function to flatten a nested array"
// DO try it yourself first, even if it takes an hour
// Your first attempt might be messy:
function flattenArray(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
// Hmm, how do I handle this?
// Let me think...
}
}
}
After wrestling with the problem, THEN use AI to:
- Check your solution
- Learn alternative approaches
- Understand trade-offs
2. Debugging Your Own Logic
Before asking AI "why doesn't this work?", practice systematic debugging:
// Your code that isn't working
function calculateDiscount(price, discount) {
return price - (price * discount);
}
console.log(calculateDiscount(100, 20)); // Expected: 80, Got: 1980
Good debugging practice:
- Add console.logs to see actual values
- Check your assumptions
- Read error messages carefully
- Use the debugger
function calculateDiscount(price, discount) {
console.log('Price:', price, 'Discount:', discount); // Ah! discount is 20, not 0.20
const discountPercent = discount / 100;
return price - (price * discountPercent);
}
When to use AI: After you've attempted debugging yourself, use AI to explain WHY your logic was wrong, not just to get a fix.
The Over-Architecture Anti-Pattern
AI tools often suggest enterprise-level patterns for simple problems. This is particularly common with ChatGPT and Claude.
The Problem
You ask: "How do I store user preferences?"
AI suggests: A complete microservices architecture with message queues, event sourcing, and CQRS patterns.
// AI's overcomplicated suggestion for a simple todo app
interface IUserPreferenceRepository {
getPreference(userId: string, key: string): Promise<Preference>;
setPreference(userId: string, key: string, value: any): Promise<void>;
}
class UserPreferenceService {
constructor(
private repository: IUserPreferenceRepository,
private eventBus: IEventBus,
private cache: ICache
) {}
async updatePreference(command: UpdatePreferenceCommand): Promise<void> {
const event = new PreferenceUpdatedEvent(command);
await this.eventBus.publish(event);
await this.cache.invalidate(`pref:${command.userId}`);
// ... 50 more lines
}
}
The Fix
For a small todo app, you probably just need:
// Simple and appropriate solution
const userPreferences = {
theme: localStorage.getItem('theme') || 'light',
sortBy: localStorage.getItem('sortBy') || 'date'
};
function updatePreference(key: string, value: string) {
localStorage.setItem(key, value);
userPreferences[key] = value;
}
Rule of thumb: Start simple. Add complexity only when you have a concrete need, not because AI suggested it.
Security and Compliance: High-Stakes Situations
AI tools are trained on public code, which often contains security vulnerabilities and outdated practices.
Never Trust AI Blindly For:
1. Authentication and Authorization
# AI might suggest this for API authentication
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get('Authorization')
if token == 'secret-key-123': # NEVER do this!
return f(*args, **kwargs)
return jsonify({'error': 'Unauthorized'}), 401
return decorated
Problems:
- Hardcoded credentials
- No token expiration
- No encryption
- Vulnerable to replay attacks
2. Data Privacy and GDPR Compliance
// AI suggestion that violates GDPR
function trackUser(userId, action) {
analytics.track({
userId: userId,
email: user.email, // PII without consent!
ipAddress: request.ip,
action: action,
timestamp: Date.now()
});
}
Action step: For security-critical code, always:
- Consult official documentation
- Follow established security frameworks (OWASP, etc.)
- Have security-focused code review
- Never assume AI knows your compliance requirements
The "It Works On My Machine" Syndrome
AI doesn't know your production environment, infrastructure constraints, or deployment context.
Environment-Specific Issues
# AI suggests this for file uploads
@app.route('/upload', methods=['POST'])
def upload_file():
file = request.files['file']
file.save(f'/uploads/{file.filename}') # Multiple problems!
return 'File uploaded'
Issues AI doesn't consider:
- Your production server might not have write access to
/uploads/ - File name conflicts
- Directory traversal vulnerabilities
- No file size limits (DoS risk)
- Not handling cloud storage (S3, etc.)
Better Approach
import os
from werkzeug.utils import secure_filename
from flask import current_app
@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return 'No file provided', 400
file = request.files['file']
# Check file size (use your config)
file.seek(0, os.SEEK_END)
size = file.tell()
if size > current_app.config['MAX_FILE_SIZE']:
return 'File too large', 413
file.seek(0)
# Secure filename and use configured upload folder
filename = secure_filename(file.filename)
filepath = os.path.join(
current_app.config['UPLOAD_FOLDER'],
f"{uuid.uuid4()}_{filename}"
)
file.save(filepath)
return {'filename': filename}, 201
Performance and Scale: AI Doesn't Know Your Traffic
AI suggestions often ignore performance implications.
// AI might suggest this for a product listing page
async function getProducts(req, res) {
const products = await Product.findAll();
// Fetch reviews for each product
for (let product of products) {
product.reviews = await Review.findAll({
where: { productId: product.id }
});
}
res.json(products);
}
This creates an N+1 query problem. With 100 products, that's 101 database queries!
Better approach:
async function getProducts(req, res) {
// Single query with JOIN
const products = await Product.findAll({
include: [{
model: Review,
attributes: ['rating', 'comment'],
limit: 5 // Only recent reviews
}],
limit: 20, // Pagination
offset: req.query.page * 20
});
res.json(products);
}
When AI Shines: The Right Use Cases
To balance this out, here are situations where AI IS incredibly helpful:
- Boilerplate code: Config files, standard CRUD operations
- Code translation: Converting between languages or frameworks
- Test generation: Creating test cases for existing code
- Documentation: Writing comments and README files
- Refactoring: Suggesting cleaner code structure
- Learning: Explaining complex code or concepts
Building Good Habits
Here's a practical workflow that balances AI assistance with skill development:
The 30-30-30 Rule
- 30% of the time: Solve problems without AI first
- 30% of the time: Use AI as a pair programmer, iterating together
- 30% of the time: Use AI to check your work and learn alternatives
- 10% of the time: Copy-paste AI solutions (only for well-understood boilerplate)
Code Review Checklist for AI-Generated Code
Before merging AI suggestions, ask:
- Do I understand every line?
- Does it handle edge cases?
- Are there security implications?
- Is it testable?
- Does it fit our architecture?
- Is it maintainable by others?
- Does it meet performance requirements?
Conclusion
AI coding assistants are powerful tools, but they're tools, not replacements for developer judgment. The developers who thrive with AI aren't those who use it the most—they're those who know when NOT to use it.
Avoid blindly accepting suggestions, especially for security, architecture decisions, or when learning new concepts. Use AI to accelerate your work, not to bypass understanding.
Remember: AI can write code, but it can't take responsibility for production outages, security breaches, or technical debt. That's still on you.
Next Steps:
- Review your recent AI-assisted code with the checklist above
- Identify one area where you've been over-reliant on AI (check out our lesson on over-reliance)
- Practice the 30-30-30 rule for the next week
The goal isn't to avoid AI—it's to use it wisely. Master that, and you'll be a better developer than those who work without AI OR those who can't work without it.