Post

Prompt Engineering for Coding Tasks — What Actually Works

Prompt engineering for coding tasks is not about magic phrases or elaborate templates. It's about giving the model what it needs to do the job. Here are the patterns I actually use — and the mistakes that waste the most time.

Prompt Engineering for Coding Tasks — What Actually Works

“Prompt engineering” has developed a reputation for being either trivially obvious or arcane dark magic, depending on who you ask. The truth is more boring: it’s just communication. Giving an AI model what it needs to do a specific job well — clearly, efficiently, without ambiguity.

For coding tasks specifically, there are patterns that consistently produce better output and mistakes that consistently produce worse output. After using Claude Code and GitHub Copilot daily for well over a year, here’s what I’ve actually learned.


The Foundation: Context Is Everything

The single biggest driver of output quality is how much relevant context you give upfront.

AI models don’t have background knowledge about your codebase, your conventions, your constraints, or your goals. Every relevant piece of context you leave out is something the model has to guess at. It will guess plausibly. Plausible is often wrong.

Before any non-trivial coding prompt, I ask myself: what does the model need to know to do this well?

Usually the answer includes:

  • What I’m trying to accomplish (the goal, not just the immediate task)
  • What constraints exist (language, framework, style conventions, performance requirements)
  • What already exists and is relevant (related code, the interfaces it needs to fit)
  • What I’ve already tried or ruled out, if applicable

Providing this context feels like extra work. It saves time in aggregate because you get useful output in fewer iterations.


Pattern 1: Goal First, Details After

State what you’re trying to accomplish before describing the specific task.

Worse:

“Write a function that filters a list of orders by date range.”

Better:

“I’m building a reporting API for a warehouse management system. I need a function that filters a list of orders by date range, where orders can have either a created_at or a shipped_at date and the filter should apply to whichever date type is specified. The orders are Python dicts from a Postgres query result.”

The second version produces code that actually fits your problem. The first produces code that fits the general description of the problem, which is often subtly different.


Pattern 2: Show the Shape You Need

If you have an existing codebase with patterns and conventions, show them. Don’t describe them.

Worse:

“Write this following our existing error handling conventions.”

Better:

Paste an existing function that uses your error handling conventions, then say: “Write the new function following the same error handling pattern.”

Models learn conventions from examples far more reliably than from descriptions. One clear example is worth several paragraphs of documentation.


Pattern 3: Specify the Constraints Explicitly

Constraints that seem obvious to you are not obvious to the model.

  • Language version: Python 3.11, not 3.8
  • Framework constraints: we use SQLAlchemy 2.0, not raw SQL
  • Performance requirements: this runs on every request, so O(n) is fine but O(n²) is not
  • Side effects: this function should not modify the input
  • Error handling: raise, return an error object, or return None? Be explicit

Every constraint you leave implicit is a coin flip. Models make plausible choices. Plausible choices accumulate into code that looks right and doesn’t quite fit.


Pattern 4: Ask for Reasoning on Complex Tasks

For tasks involving design decisions or non-obvious tradeoffs, ask the model to explain its approach before writing the code.

“Before writing the implementation, explain how you’d approach this and what tradeoffs you’re making.”

Two things happen. First, you catch misunderstandings before they’re baked into working code — it’s much easier to redirect a plan than to refactor an implementation. Second, the explanation often surfaces considerations you hadn’t thought of.

This adds thirty seconds to simple tasks. On complex tasks it saves hours.


Pattern 5: Iterate Incrementally, Don’t Correct in Bulk

When the output isn’t quite right, correct one thing at a time.

Worse:

“This isn’t quite right — the error handling is wrong, the function signature doesn’t match what I need, and the variable names don’t follow our conventions.”

Better:

“The logic is right, but update the error handling to raise a ValueError with the message format from the example I showed earlier.”

Then in the next message: “Now update the function signature to match…”

Incremental correction produces cleaner output than bulk correction because you’re giving the model clear success criteria for each step. Bulk corrections often result in one thing being fixed and another being changed in an unexpected way.


Pattern 6: Be Explicit About What Not to Do

This sounds odd but it works. If there are approaches you know won’t work — because of constraints, previous attempts, system limitations — say so upfront.

“Don’t use a third-party library for this — we can’t add dependencies to this module. The solution needs to use the standard library only.”

“We’ve already tried caching this at the application layer and it doesn’t solve the problem. Don’t suggest caching as a solution.”

Negative constraints guide the search space. Without them, models will generate plausible suggestions that you’ve already ruled out.


The Mistakes That Waste the Most Time

Prompting too vaguely and hoping for the best. You get generic output, it doesn’t fit, you either spend time refining it or give up and write it yourself. Just write the better prompt first.

Pasting too much irrelevant code. More context is not always better context. Pasting an entire codebase when the model needs one module creates noise. Be selective — give the relevant code, not everything you have.

Treating first output as final. Even with good prompts, the first output is usually 80% of the way there. Plan to iterate. One round of focused correction gets you to something you can actually use.

Asking for too much in one prompt. “Write the API, add the tests, update the documentation, and make sure it handles all edge cases” is too many tasks at once. Break it into steps. Each step produces better output when it’s the only thing being asked for.

Not verifying the output. Models produce confident-looking code that sometimes has subtle bugs. Always read the output before using it. This sounds obvious. Engineers who’ve been using AI tools for six months sometimes forget it.


For GitHub Copilot Specifically

Copilot reads your current file and open tabs for context. A few things that help:

  • Comment-driven development. Write a comment describing what the next function should do before writing it. Copilot will use the comment as a strong signal for what to generate.
  • Meaningful names. Functions and variables with clear, descriptive names generate much better completions than abbreviated or generic names.
  • Keep related context in open tabs. If you’re implementing something that needs to match an existing interface, keep the interface file open. Copilot sees open tabs.

The Meta-Point

Good prompts are good communication. You’re describing a problem clearly enough that someone who doesn’t know your codebase, your business, or your constraints can nonetheless produce a useful output.

That skill — describing problems clearly and precisely — is independently valuable. Engineers who are good at prompting AI tools are usually also good at writing design docs, opening useful GitHub issues, and making effective technical arguments. The prompting discipline makes you more precise about what you actually need.


Day 10 of the 30-Day AI Engineering series. Previous: Claude CoWork — Async AI Pair Programming in Practice.

This post is licensed under CC BY 4.0 by the author.