1. Think before coding
When a request is ambiguous, LLMs tend to pick an interpretation silently and implement it. The result looks complete but is built on assumptions the user never validated.Hidden assumptions: exporting user data
Request: “Add a feature to export user data”Correct approach — surface assumptions first
2. Simplicity first
LLMs frequently reach for abstractions and design patterns before any complexity is warranted. The result is code that follows best practices but is harder to read, harder to test, and solves a problem that doesn’t exist yet.Over-abstraction: discount calculator
Request: “Add a function to calculate discount”Correct approach — minimum code that solves the problemAdd complexity only when you actually need multiple discount types. If that requirement comes later, refactor then.
3. Surgical changes
When fixing a bug, LLMs often “improve” unrelated code in the same function — reformatting, renaming, adding type hints, strengthening validation. Each individual change seems reasonable, but together they produce a diff that is hard to review and risky to ship.Drive-by refactoring: fixing an email validation bug
Request: “Fix the bug where empty emails crash the validator”Correct approach — only the lines that fix empty email handlingEvery changed line traces directly to fixing the empty email crash.
4. Goal-driven execution
Vague instructions produce vague plans. When a task has no explicit success criteria, the model improvises — and you have no way to verify the result is correct until something breaks in production.Multi-step with verification: adding rate limiting
Request: “Add rate limiting to the API”Correct approach — incremental steps, each independently verifiable
Anti-patterns summary
| Principle | Anti-pattern | Fix |
|---|---|---|
| Think before coding | Silently assumes file format, fields, scope | List assumptions explicitly, ask for clarification |
| Simplicity first | Strategy pattern for single discount calculation | One function until complexity is actually needed |
| Surgical changes | Reformats quotes, adds type hints while fixing bug | Only change lines that fix the reported issue |
| Goal-driven execution | ”I’ll review and improve the code" | "Write test for bug X → make it pass → verify no regressions” |
Key insight
The “overcomplicated” examples are not obviously wrong — they follow design patterns and best practices. The problem is timing: they add complexity before it is needed, which makes code harder to understand, introduces more bugs, takes longer to implement, and is harder to test.Good code is code that solves today’s problem simply, not tomorrow’s problem prematurely.The simple versions are easier to understand, faster to implement, easier to test, and can be refactored when complexity is actually needed.