How to Prevent AI Coding Assistants from Copying Bad Code Patterns
Problem
I noticed something disturbing last week. My AI coding assistant started generating code with the same bad patterns I’d been trying to eliminate from my codebase. It was like watching a child learn bad habits from an irresponsible parent—except I was that parent, and I was teaching my AI assistant to write terrible code.
Here’s what happened: I asked Claude Code to implement a simple user update function. Instead of following my carefully crafted immutability rules, it spat out code that mutated objects left and right. When I looked closer, I realized it had been reading my old legacy files—files I hadn’t gotten around to refactoring yet.
The real kicker? A Reddit thread confirmed this is a widespread problem. One developer put it perfectly: “The pattern where it copies from older bad code is the real killer. A slightly wrong abstraction in session 1 becomes destructive by session 10.” Another added: “It’s a context problem more than a model problem. Claude reads 15+ files and patterns off whatever it finds—including the worst code.”
The Compounding Problem
Let me show you how this snowballs out of control:
Session 1:AI reads legacy code with mutation↓AI generates new code with mutation (slightly worse)↓Session 5:AI reads Session 1's code + more legacy↓AI generates even worse patterns (duplicated logic, deep nesting)↓Session 10:Codebase is now polluted with AI-generated bad patternsthat compound the original problemsThis isn’t hypothetical. I saw it happen in my own codebase. The AI would read a function with a subtle mutation bug, then propagate that pattern across 20 new files. Each iteration made the problem slightly worse, like a game of telephone where the message gets more distorted with each repetition.
My Failed Attempts (and What I Learned)
Attempt 1: Just Tell the AI to “Write Better Code”
I tried prompting with: “Write clean, maintainable code following best practices.”
Result: The AI nodded politely and then proceeded to generate the same bad patterns. Why? Because it was still reading my terrible legacy files as examples. The prompt was too vague to override the concrete examples it was seeing in my codebase.
Attempt 2: Delete All the Bad Code
I spent a weekend refactoring furiously, thinking if I cleaned up the codebase, the AI would have nothing bad to copy.
Result: This was unsustainable. I can’t refactor 50,000 lines of legacy code overnight. Plus, I still needed to work on actual features. The AI would read whatever remained—including the ugly parts I hadn’t gotten to yet.
Attempt 3: Explicit Guidelines + Context Curation
This one worked. I created a CLAUDE.md file at the root of my project with explicit coding standards, and I started being intentional about which files the AI should use as reference patterns.
Result: The AI’s code quality improved dramatically. But this required understanding three critical strategies.
Strategy 1: Context Curation
The AI learns from what it reads. If you give it garbage, it learns garbage. The fix is to explicitly specify which files to use as positive examples.
Before (AI reads everything):├── legacy/│ ├── spaghetti-code.js ← AI reads this and copies patterns│ └── mutation-helpers.js ← And this terrible file too├── src/│ ├── clean-utils.ts ← AI barely notices this one│ └── good-patterns.ts ← Or this well-designed module└── tests/ └── flaky-tests.test.js ← AI learns bad testing patterns
After (AI reads curated files):├── .claudeignore ← Explicitly exclude legacy/├── CLAUDE.md ← Document which files are good examples└── src/ ├── clean-utils.ts ← AI uses THIS as reference └── good-patterns.ts ← And THIS for patternsI created a .claudeignore file (like .gitignore) to explicitly exclude legacy directories:
legacy/old-experiments/deprecated/temp/*.backup.js*.test.jsThen in my CLAUDE.md, I documented the files that represent good patterns:
## Reference Files
When implementing similar functionality, study these files:- src/utils/immutable-helpers.ts (for immutability patterns)- src/services/user-service.ts (for clean service layer)- src/hooks/use-api-call.ts (for React hooks patterns)Strategy 2: Explicit Coding Standards
Documentation in CLAUDE.md overrides implicit patterns the AI might infer from reading files. This is crucial—you need to make the invisible visible.
# Coding Standards
## Immutability (CRITICAL)
ALWAYS create new objects, NEVER mutate:
```typescript// WRONG: Mutationfunction updateUser(user, name) { user.name = name // MUTATION! return user}
// CORRECT: Immutabilityfunction updateUser(user, name) { return { ...user, name }}Error Handling
ALWAYS handle errors comprehensively:
try { const result = await riskyOperation() return result} catch (error) { console.error('Operation failed:', error) throw new Error('Detailed user-friendly message')}The key is the word "CRITICAL"—Claude Code pays attention to these emphasis markers and will prioritize these rules over whatever patterns it sees in your files.
I learned this the hard way. Without explicit standards, the AI assumed my legacy code was intentional. Once I documented "CRITICAL: Always use immutability," it started catching mutations in my code and refusing to generate them.
## Strategy 3: Session Memory & Feedback Loops
Here's something I discovered by accident: giving the AI a place to document its mistakes dramatically improves future sessions. I created a `.claude/session-notes.md` file and instructed the AI to write down what went wrong after each task.
```text title="session-notes.md"# Session Notes
## 2026-03-10: User Update Feature
### What went wrong:- Initially used mutation in updateUser()- Forgot to handle the case where user is null- Deep nesting exceeded 4 levels in validation logic
### What I learned:- Always check for null/undefined at the start of functions- Use early returns to avoid deep nesting- Refer to src/utils/immutable-helpers.ts for update patterns
## 2026-03-09: API Integration
### What went wrong:- Hardcoded API URLs instead of using environment variables- Forgot to add retry logic for transient failures- Mixed concerns: business logic with API calls
### What I learned:- Use process.env.API_BASE_URL for all endpoints- Check src/services/api-client.ts for retry patterns- Keep business logic separate from API layerThis creates a feedback loop. The AI reads these notes in subsequent sessions and avoids repeating the same mistakes. One Reddit commenter said: “Giving the agent a place to note side-findings and forcing it to write down what went wrong after each task helps” (score 3). They were absolutely right.
The psychological effect is interesting: the AI takes ownership of its mistakes when it documents them. It becomes more careful, more deliberate.
Progressive Context Loading
Don’t dump your entire codebase at once. The AI has limited context window, and showing it too much information—including contradictory patterns—causes confusion.
BAD: Load 50 files at once├── 10 files with good patterns├── 15 files with mediocre patterns└── 25 files with terrible patterns→ AI gets confused, mixes patterns, generates inconsistent code
GOOD: Load progressively based on taskStep 1: Load CLAUDE.md (coding standards)Step 2: Load 1-2 reference files for current patternStep 3: Load only the files needed for current task→ AI has clear signal, generates consistent codeI implemented this by being specific about which files to read. Instead of asking “implement a user service,” I say “implement a user service following the patterns in src/services/user-service.ts.”
Real Example: Before and After
Here’s the actual mutation propagation I witnessed:
// Legacy code with mutationfunction updateUser(user, name) { user.name = name // MUTATION - bad pattern return user}// AI-generated code with compounded problemsfunction processOrder(order, items) { order.items = items // MUTATION (learned from Session 1) order.updatedAt = Date.now() // MUTATION (learned from Session 3) order.status = 'processing' // MUTATION (learned from Session 5)
// Deep nesting (learned from Session 7) if (order.items) { if (order.items.length > 0) { for (let i = 0; i < order.items.length; i++) { if (order.items[i].price > 0) { // More nesting... } } } }
return order}After implementing the strategies above:
// AI follows documented standardsfunction processOrder(order: Order, items: Item[]): Order { if (!order || !items) { throw new Error('Order and items are required') }
const validItems = items.filter(item => item.price > 0)
return { ...order, items: validItems, updatedAt: Date.now(), status: 'processing' }}The difference is stark. The AI went from generating compounded bad patterns to following explicit standards that override the legacy code it reads.
What I Wish I Knew Earlier
-
You can’t refactor your way out of this - I tried cleaning up my codebase, but the AI learns faster than I can refactor. Documentation is the forcing function.
-
The AI doesn’t distinguish between “good” and “bad” code - It treats all code equally as learning material. You must make the distinction explicit.
-
Context is a double-edged sword - More context isn’t always better. Curated context beats comprehensive context every time.
-
Feedback loops compound positively too - Just as bad patterns compound, good documentation compounds. The AI gets better over time as it reads its own notes.
-
Prevention beats correction - It’s easier to set up CLAUDE.md before starting a project than to fix AI-generated bad code after the fact.
Summary
In this post, I shared how AI coding assistants can learn and propagate bad code patterns from your existing codebase, and the strategies that actually work to prevent this. The key point is that context curation and explicit documentation standards override the implicit patterns the AI infers from reading files. I experimented with three approaches—vague prompts, manual refactoring, and explicit guidelines—and only the third worked. The combination of .claudeignore, detailed CLAUDE.md standards, and session feedback loops creates a virtuous cycle where the AI improves over time instead of degrading. The upfront investment in documentation pays off quickly: my AI assistant now generates better code than I could write manually, because it’s learned from carefully curated examples and documented mistakes.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments