Prompt Engineering for Developers: What Actually Changes Output Quality
The prompting techniques that measurably improve results for coding tasks — context, specificity, examples and verification — without the mysticism.
Table of contents
- Context is the highest-leverage variable
- State constraints, not just goals
- Ask for reasoning before code on hard problems
- Give examples for anything with a format
- Iterate on the diff, not the whole file
- Verification is your job
- Where AI genuinely helps, and where it does not
- Frequently asked questions
- Does "you are an expert…" framing help?
- Should I use one long prompt or a conversation?
- How do I stop it inventing APIs?
- Is it worth writing project-level instructions?
- Related reading
- References
Most of what improves LLM output for coding work is unglamorous: give it the context it needs, state the constraints, and verify the result. The techniques below are the ones that reliably help.
Context is the highest-leverage variable#
A model cannot infer your codebase's conventions from a one-line request. The single biggest quality improvement comes from supplying what it cannot know:
Weak: "Write a function to validate an email."
Better: "Write a TypeScript function to validate an email for a Next.js Server Action. We use Zod v4 for validation and return { status, message } objects rather than throwing. Max length 254 per RFC 5321. Match the style of this existing action: [paste]."
The second version fixes the language, the framework, the library and version, the error convention, a specific requirement, and the house style. Each of those is something the model would otherwise guess.
State constraints, not just goals#
Constraints are what turn a plausible answer into a usable one:
- Versions. "React 19", "Tailwind v4", "Zod v4" — APIs differ between majors and a model will happily write the older one.
- What not to do. "Do not add a dependency." "Do not use
any." "Keep it in one file." - The shape of the answer. "Return only the diff." "No explanation." "Include the test."
- Non-functional requirements. "This runs in the browser, so no Node APIs." "It must work without JavaScript."
Ask for reasoning before code on hard problems#
For anything with a design decision in it, asking for the analysis first produces better code than asking for code:
Before writing anything, list three approaches to rate-limiting this endpoint, with the trade-off of each for a serverless deployment. Then recommend one and explain why.
This surfaces the assumptions early, where they are cheap to correct. Asking for code first means the reasoning is post-hoc justification of whatever it happened to write.
Give examples for anything with a format#
Few-shot examples beat description for structured output:
Convert these to conventional commit messages.
Input: "fixed the thing where json broke on big numbers" Output:
fix(json): preserve precision for integers above 2^53Input: "added dark mode" Output:
feat(ui): add dark mode with system preference detectionNow: "made the tools page load faster by fixing the radix import"
Two or three examples pin the format far more reliably than a paragraph explaining it.
Iterate on the diff, not the whole file#
Once you have working code, do not ask for regenerations — ask for changes:
This works. Now: (1) handle the empty-input case, (2) the error message should include the line number, (3) leave everything else unchanged.
Numbered, specific, and explicitly bounded. Regenerating a whole file invites unrelated changes you then have to review.
Verification is your job#
This is the part no prompting technique replaces. Models produce confident, plausible, wrong code — most commonly:
- APIs that do not exist. A method that sounds right for the library but was never in it.
- Version drift. Correct code for an older major version.
- Subtly wrong edge cases. Off-by-one, an unhandled null, a regex that fails on the boundary case.
- Security gaps. Missing authorisation, unvalidated input, a hand-rolled crypto primitive.
Practical defences: ask for tests alongside the code and run them; check any unfamiliar API against its real documentation; and be most suspicious where the code looks most confident.
Where AI genuinely helps, and where it does not#
Helps a lot: boilerplate, test scaffolding, translating between languages, explaining unfamiliar code, writing regexes, migration mechanics, documentation drafts, naming.
Helps less: architecture decisions that depend on constraints you have not written down, debugging something that needs a running system, anything where being subtly wrong is expensive and hard to detect.
The dividing line is whether you can cheaply verify the answer. Where verification is cheap — a test passes, a type checks — AI is a large win. Where verification is expensive, the risk of a confident wrong answer outweighs the speed.
Frequently asked questions#
Does "you are an expert…" framing help?#
Marginally at best with current models. Concrete context and constraints help enormously. Spend the words there.
Should I use one long prompt or a conversation?#
A conversation, for anything non-trivial. It lets you correct course early instead of reviewing a large wrong answer.
How do I stop it inventing APIs?#
Paste the actual type signatures or documentation into the prompt, and ask it to note explicitly where it is unsure. Grounding beats correcting.
Is it worth writing project-level instructions?#
Yes — a file describing your stack, conventions and constraints, supplied with each request, removes the need to restate them and makes output much more consistent.