← All writing

Prompt Engineering for Software Developers

Prompt engineering for developers is not about clever phrases. A useful prompt works like a small technical brief: role, task, context, issue, constraints, examples, output format, and done criteria.


Most weak prompts have the same problem as weak tickets: they ask for help without giving enough shape to the work.

“Fix this bug” is not a prompt. It is a wish.

A useful developer prompt reads more like a small technical brief. It gives the model a role, names the task, explains the issue, includes the relevant context, sets the boundaries, shows examples when needed, and says what a good answer should look like.

That sounds like a lot, but it is usually only a few lines.

The format I use

For coding work, this is the prompt shape I reach for:

Role: You are a senior Laravel developer.
Task: Fix the failing validation behavior.
Context: This endpoint uses Form Requests and the standard API response envelope.
Issue: Empty email values currently pass validation.
Constraints: Do not change the response shape or add dependencies.
Examples: Follow the existing test style in UserRegistrationTest.
Output: Explain the root cause, patch the code, and list tests run.
Done when: Validation failure returns 422 and tests pass.

You do not need every line every time. For a small question, two or three are enough. For code changes, production bugs, or anything that touches multiple files, the structure saves time.

OpenAI’s basic prompting guidance comes down to the same pattern: outline the task, give context, and describe the ideal output. The OpenAI API guidance also recommends putting instructions early, separating instructions from context, being specific about format, and using examples when output shape matters. Anthropic’s prompt engineering overview starts even earlier: define success criteria and have a way to test them.

That last part matters for developers. A prompt is not finished when the model answers. It is finished when the result can be checked.

Role: give it a useful character

The role is the “you are a…” line:

You are a senior TypeScript developer.

Or:

You are a careful code reviewer focused on bugs, regressions, and missing tests.

This is useful, but it is not magic. A role does not replace context. “You are a senior developer” will not make the model understand your route structure, testing style, or deployment constraints.

I use role to set the lens. Backend architect. Laravel maintainer. SwiftUI reviewer. Security-minded API reviewer. Technical editor.

Bad:

You are the best programmer in the world. Fix my app.

Better:

You are a senior Laravel API developer.
Review this controller for response-shape bugs, validation gaps, and missing tests.

The second version tells the model what kind of judgment to apply.

Task: use a verb that matches the phase

The task line should start with the real action.

Not all prompts are implementation prompts. Sometimes you want exploration. Sometimes you want review. Sometimes you want a plan before anyone edits a file.

Useful verbs:

  • Explain
  • Trace
  • Compare
  • Plan
  • Fix
  • Refactor
  • Review
  • Draft
  • Test

The verb changes the result.

Explain how writing posts are loaded and rendered.

is different from:

Fix the writing post route.

And both are different from:

Plan the safest way to add draft support to writing posts.
Do not edit files yet.

When I am unsure, I ask for exploration first. It is cheaper to correct a misunderstanding in a paragraph than in a diff.

Context and issue: explain what is happening

The context tells the model where it is working. The issue tells it what is wrong.

Weak prompt:

The form is broken. Fix it.

Better:

Context: The contact form is handled by ContactForm.astro and posts to /api/contact.
Issue: When the email field is empty, the UI still shows the success state.
Expected: Empty email should show the existing validation message and avoid the success state.

For debugging, paste the real error. Do not summarize it away.

Error:
TypeError: Cannot read properties of undefined (reading 'slug')
File: src/pages/writing/[slug].astro
Command: pnpm build

Exact filenames, commands, and messages are boring details. They are also the details that help the model find the right place.

Claude Code’s own guidance says the same thing in a practical way: give the outcome, paste full errors, point at files when you know them, and say what done looks like.

Constraints: protect the codebase

Constraints are where a prompt becomes safe.

Without constraints, a small fix can turn into a rewrite. The model is trying to be helpful. It may add a dependency, rename a prop, change a response shape, or “clean up” unrelated files unless you tell it not to.

For developer work, constraints often look like this:

Constraints:
- Do not change public API.
- Do not add dependencies.
- Do not edit generated files.
- Keep the change limited to src/content/writing.
- Follow the existing markdown style.
- Run tests and build after editing.

For a review prompt:

Constraints:
- Lead with bugs and regressions.
- Skip style comments unless they affect maintainability.
- Include file and line references.

For a refactor:

Constraints:
- Preserve behavior.
- Keep exported function names unchanged.
- Add tests before changing implementation.

Most bad generated code is not bad because the model cannot code. It is bad because the prompt did not say what must stay stable.

Examples: show the local pattern

Examples are useful when the output needs to match a local style.

If the project has a response envelope, show it. If tests follow a naming style, point to one. If content has a voice, reference an existing article.

Example:

Follow this test style:

it('renders only featured projects on the home page', async () => {
  // existing assertion style here
});

Or:

Use this response shape:

{
  "success": false,
  "message": "Validation failed",
  "data": null,
  "errors": { "email": ["The email field is required."] }
}

One good example is enough. Five random examples just add noise.

Output format: make it easy to review

If you do not ask for a shape, you get whatever shape the model chooses.

For debugging, I like:

Output:
1. Root cause
2. Evidence from the code
3. Smallest fix
4. Tests to run
5. Risks or assumptions

For a code review:

Output:
- Findings first, ordered by severity.
- Include file and line reference.
- Explain the user-visible risk.
- If there are no findings, say that clearly.

For content:

Output:
- Keep the existing title and slug.
- Rewrite the body only.
- Match the structure of the existing writing posts.
- Avoid marketing tone.

Output format is not decoration. It makes the answer reviewable.

Done criteria: say what finished means

This is the line developers skip most often.

Done when:
- Empty email returns 422.
- Existing response shape is unchanged.
- One validation-failure test is added.
- pnpm test passes.

That turns the prompt into something testable.

Without done criteria, the model may stop at “code changed.” That is not enough. The real target is behavior verified.

Three practical prompts

Bug fix:

Role: You are a senior Laravel developer.
Task: Fix a validation bug.
Context: User registration uses Form Requests and the standard API response envelope.
Issue: Empty email values pass validation and create users with invalid data.
Constraints: Do not change the response shape, database schema, or route names.
Examples: Follow the existing UserRegistrationTest style.
Output: Explain root cause, make the smallest patch, and list tests run.
Done when: Empty email returns 422 and the full test suite passes.

Code review:

Role: You are a senior code reviewer.
Task: Review this diff for bugs.
Context: This is an Astro portfolio site using content collections.
Issue: Three new writing posts were added.
Constraints: Ignore subjective style unless it affects readability or build output.
Output: Findings first with file references, then missing tests or residual risk.
Done when: The review identifies real regressions or says there are none.

Content rewrite:

Role: You are a technical editor.
Task: Rewrite this article so it matches the existing writing style.
Context: Existing posts use a strong opening, concrete examples, practical sections, and a short closing.
Issue: The current draft is too list-heavy and reads flat.
Constraints: Keep title, slug, date, and tags unchanged.
Examples: Match the rhythm of the Astro and Laravel API response posts.
Output: Return the rewritten markdown body.
Done when: The article feels like a practical developer note, not a generic guide.

The practical version

Prompt engineering is not about clever wording. It is about removing ambiguity.

Give the model a role. Name the task. Explain the context and issue. Set constraints. Show examples when style matters. Ask for a reviewable output. Say what done means.

That format will not make every answer perfect. It will make most answers easier to judge, and that is what developers need.