Skip to content
AI Field Notes

Part I · Understanding the Models

03

Prompting and Pitfalls

The mindset shift that makes AI coding actually work

Have you ever had the exact same task produce great results one day and terrible results the next, from the same model?

You paste the same task into the same model on two different days and get completely different results. One session the code is clean and exactly what you wanted. The other, it's subtly wrong in a way you only catch three days later when something breaks.

Same model. Same task. Different output.

It's usually not the model being unpredictable — it's what you gave it. The context, the framing, the constraints you forgot to mention, the goal you assumed it could infer. Good results come down to two learnable habits: giving the model what it needs to make good decisions, and noticing when it's heading somewhere you didn't intend. Neither habit is the default. Both are worth building.

Prompting That Actually Works

Ask clarifying questions first

If anything in the request is unclear, ask before writing code. One clarifying question upfront is almost always faster than confidently solving the wrong problem.

This sounds obvious — it isn't the default. Most models (and most developers) have a bias toward action. The instinct is to start, not to pause. A 30-second clarification usually produces a better result than the best possible answer to the wrong question.

The prompt that unlocks this: "Before you start, ask me anything that would help you do this better." That one sentence shifts the model from execution mode to clarification mode.

Don't be too brief, don't be too rigid

Both extremes fail. Two sentences isn't enough context for good decisions. But over-specifying every detail prevents the model from applying judgment where judgment is actually useful.

Give it enough context to understand the problem and the constraints, then let it work.

Too brief: "Add error handling to this function." Too rigid: "Add a try-catch block starting at line 12, catch the TypeError, log it with console.error, and return null." Right: "Add error handling to this function. It's called from a React component, so failures should be catchable without crashing the UI. Use whatever pattern fits the existing error handling in this file."

Explain the why

Models make better decisions when they understand the goal, not just the task. "Why" gives them the context to make judgment calls that align with what you actually want.

"Refactor this component" — toward what? "Refactor this component to make it easier to add new props without touching the render logic" gives the model a direction and a way to evaluate its own output.

Show, don't describe

Want the model to match your style? Show it examples. Don't describe your style — show it.

"Follow the same pattern as the existing routes in src/api/" beats "use RESTful conventions and consistent naming" every time. The model can read those routes and extract the actual pattern, including things you'd never think to describe explicitly.

Work in small iterations

Long, multi-step prompts that ask for everything at once tend to produce worse results than breaking things into steps. More context, more assumptions, more output in one shot — errors compound.

Spec first, then plan, then implement, then review. Each step is a separate prompt. You review each output before moving to the next.

Step 1: "Given this spec, what's your plan? What files will you create or modify?" Step 2: "The plan looks good. Now implement step 1 only." Step 3: "Good. Now implement step 2."

Make it think first

For anything non-trivial, ask the model to think before it codes. "What are the tradeoffs here?" or "Walk me through your approach before implementing" — this surfaces assumptions and lets you correct them before they're baked into the output.

Set clear expectations

Be explicit. "Give me 3 options" means 3 options, not 1 with a note that alternatives exist. "Refactor but don't change the public API" means the API stays the same. "Write just the test, not the implementation" means just the test.

Clarify what's out of scope

The constraint most people forget. Telling the model what not to do is as important as telling it what to do.

"Don't modify files outside src/components." "Don't refactor existing code unless I ask." "Don't add error handling — I'll do that separately." These prevent the most common scope creep: the model helpfully doing things you didn't ask for.

Common Pitfalls

The Doom Spiral

You ask the model to fix a bug. It patches it. You run the code — still broken, slightly differently. You paste the new error back. It patches that. The patches start referencing each other. The diff grows. The original problem is buried under layers of compensation.

Each patch feels like progress. Collectively they're compounding the problem.

Why does this happen? The model doesn't have a strong signal that it's going wrong. You keep providing new errors, which it reads as "keep trying this approach." It doesn't have the instinct to say "wait, we're solving the wrong problem."

When you've seen two or three patches fighting each other, stop. Don't paste the next error. Instead: "Step back — what's the actual root cause here? Ignore the patches we've tried. What should the fix look like from scratch?" This resets the context and usually surfaces the real issue fast.

The other prevention: commit before you start. If the spiral happens, you can reset to a clean state rather than untangling a mess.

"Looks Fine, Must Be Fine"

The model hands you code. It runs. Tests pass. You skim it, nothing obviously wrong, merge it.

Three days later a bug surfaces that was sitting right there in the code you skimmed.

This one's dangerous because it feels like success. The code works — until it doesn't. And because you didn't really read it, you don't own it. When it breaks, you're debugging code you don't understand.

AI-generated code looks polished. Formatted correctly, reasonable variable names, familiar patterns. The visual signal of "good code" is there even when the logic is subtly wrong. Easy to skim past a wrong assumption when everything looks clean.

So: read every line the model writes before accepting it. Not skim — read. If you can't explain what a block does, ask the model to explain it. If the explanation doesn't hold up, don't merge it.

Yes, this slows you down. Worth it. The goal isn't to generate code faster — it's to ship code you understand and can maintain.

Complexity Creep

You ask for a simple feature. The model gives you a factory, an interface, a strategy pattern, and three layers of abstraction. Technically correct. Completely unmaintainable for a feature that didn't need any of that.

Models have a bias toward "clever." They've seen a lot of over-engineered code in training and they pattern-match to what looks sophisticated. Simple, direct code is underrepresented relative to how often it's the right answer.

If you're reading the model's output thinking "I wouldn't have built it this way" — pay attention. Not because you're always right, but because complexity you didn't choose is complexity you'll have to maintain.

The fix is straightforward: add a constraint. "Keep this as simple as possible. Prefer direct code over abstractions. Don't introduce patterns unless they're clearly necessary." Often enough to shift the output significantly.

If it still comes back over-engineered, ask: "Is there a simpler version that solves the same problem?" Usually there is.

Even better — make simplification its own pass. Get to working code first. Then: "Preserve behavior. Reduce nesting. Remove unnecessary abstractions. Keep the public API the same." Models are often better at simplifying something concrete than generating the simplest version on the first try.

This is especially true with proactive agents. Their first instinct is "be thorough," which can mean extra helpers, layers, and cleanup you didn't ask for. A simplification pass turns that energy into something useful: keep what matters, cut what doesn't.

If you use Claude Code, the official code-simplifier plugin automates this pass — it refines recently modified code for clarity and removes unnecessary abstractions while preserving behavior. Install it from the plugin marketplace, then invoke it as a dedicated step after getting working code rather than as your first move.

Outdated Patterns

Models have a training cutoff, and the AI tooling space moves fast. A model might suggest something that was best practice eighteen months ago and has since been superseded — or deprecated entirely.

Next.js APIs, React patterns, AI SDK usage — I've seen the model confidently suggest something that's correct for an older version and wrong for the current one.

When a model suggests an approach for something that changes frequently (framework APIs, AI SDKs, tooling config), ask yourself: "Is this how I'd do it today?" If you're not sure, check the docs before implementing.

This isn't about distrust — it's about knowing where the model's knowledge has gaps. Stable patterns (data structures, algorithms, general architecture) are usually reliable. Fast-moving APIs and tooling? Verify.

Stay-sane checklist

  • Can I explain this design to someone else?
  • Did I read all the generated code, not just skim it?
  • Is this the simplest version that solves the problem?
  • Did I ask for a simplification pass after the first working version?
  • Are there any APIs or patterns here I should verify against current docs?
  • Would I have built it this way if I'd written it myself?

Try it yourself

Scenario Lab

Real tasks, real model outputs — see which model wins and why

Paste a stack trace and get a plain-English explanation with a fix suggestion.

~0.8k in~0.5k outsignal: speed

Fast models handle this as well as expensive ones. The answer is either right or obviously wrong — no back-and-forth needed.

🌸
Claude Haiku 4.5Best pickrecommended

Fast, correct, no fluff

What it produced

The error occurs because `user` can be `undefined` when the component first renders. Add a null check: `if (!user) return null;` before accessing `user.name`.

What went right

  • Identified root cause immediately
  • Gave a one-line fix
  • No unnecessary explanation

Cost note

Negligible at any volume

token cost

$0.0033/run

💎
Gemini 3 FlashGood fit

Correct but slightly verbose

Claude Sonnet 4.6Use with care

Correct, but added unrequested refactor suggestions

🧠
Claude Opus 4.6Avoid

Thorough but massively over-engineered for this task

Bottom line

Haiku is fast, cheap, and more than capable for error explanation. Paying for Sonnet or Opus here is waste.

Outputs are curated from real usage. Prices from official API docs, verified 2026-03-20. Anthropic · OpenAI · Google · DeepSeek · Cursor