Prompt Engineering for Developers: Code Generation with AI

Understanding what prompt engineering really means here

At first glance, the phrase prompt engineering sounds like a buzzword someone invented at a startup pitch deck convention. But in the context of developers building code generation pipelines using AI, the “prompt” is the only tool we have to steer models like GPT, Claude, or Gemini toward specific results. It’s not about writing pretty prompts — it’s about designing input text formats that consistently yield useful code output.

Thank you for reading this post, don't forget to subscribe!

For example, if you give GPT-4 this:

// Write a function that sums an array

You might get different formatting each time, or inconsistent languages. Add a surrounding structure in your prompt like this:

Please respond ONLY with valid JavaScript code, no explanations.

Requirement: Write a function called sumArray that takes an array of numbers and returns their sum.

And suddenly, every output sticks to format and stays in JavaScript. Prompt engineering is about enforcing deterministic patterns into a model that’s prone to wandering.

In actual projects, I’ve noticed models often hallucinate helper functions if the prompt doesn’t isolate scope. For example, one of our prompts asked the model to create a React form with validation — but with no instructions on using or avoiding 3rd-party libraries, it randomly chose Formik one day and React Hook Form the next. Small changes in phrasing, like “without external libraries,” avoided that divergence.

Prompt StyleCommon Model BehaviorFix
“Write a function to do X”Returns incomplete boilerplate; random languageAdd language, structure, and scoping rules
“Create a React signup form”Injects external libs like FormikClarify: “with native React only”
Long speculative promptOutput combines code with explanation or examplesPREFIX: “Only return code block. No explanation.”

Ultimately, prompt engineering is less about cleverness than predictability — you’re debugging phrasing instead of syntax.

How to write AI prompts that generate working code

The trick to getting reliable responses from code generation AI boils down to one thing: Your prompt IS the API. It’s the function signature for what you want the model to output. That means you need to treat it like code: version it, test it, and fail loudly when it drifts.

Here’s a prompt structure that worked consistently in my own tests for generating Express endpoints:

You are a senior backend engineer. Your job is to return ONLY Node.js Express code.

Create an endpoint that:
- Accepts a POST
- URL: /api/contact
- Fields: { name, email, message }
- Validates that email is valid
- Uses async/await only
- No extra comments or wrapper text

That gets me predictable results most of the time. Occasionally, the model still injects explanations above the code. When that happens, I update the prompt to begin with “Output only valid Express code — no text, no comments, only JavaScript.”

Another common trap: asking the model to write a “full program.” That often causes it to create mock data, require nonexistent modules, or include console.logs you didn’t ask for. You can dodge this with isolated responsibilities:

Write only the route handler.
Assume Express is already set up.
No test code.

Also — always test edge cases. I gave a prompt once asking GPT-4 to generate a filtering function for search results that avoid lowercase profanity words. But it missed edge cases like spacing (“f u” vs. “fu”), and would sometimes use outdated regular expressions like /\b/ patterns that fail with emojis.

This also happens when you’re writing token-sensitive code prompts. Use explicit constraints like:

Limit output to 50 lines MAX.
No helper functions.

In the end, writing better prompts for code isn’t creative writing — it’s debugging AI comprehension.

Prompt templates for consistent code generation

Instead of typing out new prompts each time, use dynamic templates with variable injection. These work great when building AI coder tools or internal dev utilities.

I use this JSON configuration format internally:

{
  "role": "You are a senior frontend React developer.",
  "format": "Output only valid JSX, return nothing else.",
  "requirements": [
    "Build a form with {{FIELD_COUNT}} fields",
    "Field names: {{FIELD_NAMES}}",
    "On submit, call {{API_ENDPOINT}} via fetch"
  ]
}

This way, your automation tool or CLI can inject the variables on the fly. Using a template engine like Mustache or even simple string replacement lets you reuse prompt logic without retyping nuance each time.

If you’re doing this in a web app that gives a GPT API response, use hidden fields to allow versioned templates. Log which prompt version produced which code, especially when something breaks in integration. Once, we deployed a UI where one prompt version used lowercase input names and another used camelCase. The result? Nothing was actually wired up, because formData.name vs. formData.Name matters.

Template systems also let you bake-in safety clauses. If you’re generating shell scripts or SQL migrations, a template like this reduced drama for us:

ALWAYS output code in a fenced code block
Use only safe, idempotent shell commands.
NEVER use rm -rf or unverified sudo commands.

As a final point, prompt templates turn risky one-off queries into stable interfaces.

Integrating prompt logic into dev workflows

If you’re a dev using VS Code and your workflow already includes LSP (Language Server Protocol) support, then AI prompt integration might look like Copilot or CodeWhisperer overlays. But the real win — especially in team environments — comes from standardizing prompts into tools you already use, not just clicking suggestions.

In our own setup, we use:

  • Makefiles with AI targets: e.g. make endpoint name=resetPassword
  • VS Code task.json configs that inject typed Prompts into OpenAI calls and pipe into new files
  • Precommit hooks that run LLM-assisted code review on staged changes — auto-annotating security concerns

Here’s how one Makefile target works, simplified:

generate-endpoint:
    @echo "Generating $name…"
    openai api completions.create \ 
        --prompt "$(shell cat prompts/endpoint.txt | sed 's/{{NAME}}/$(name)/g')" \
        --max-tokens 500 > src/endpoints/$(name).ts

Doing it this way means new devs don’t need to invent what the prompt should say — just trigger the build script. That alone has saved us dozens of hours debugging prompt phrasing drift.

When something misfires, we know it’s either the model (GIGO — garbage in, garbage out) or an outdated prompt input. In either case, we compare the logs: what version of the template ran, what variables were passed in, what the raw model output looked like.

This isn’t magic — it’s a prompt engineering deployment strategy anyone can build.

When prompt tuning backfires or stalls

There are moments where, no matter how you nudge or adjust your prompt, models persist in giving off-track output. I’ve had this happen even after 15+ versions of a single prompt. It helps to recognize some of the common root causes:

  • The model is trained contradictorily: A prompt like “Do not explain anything” runs head-first into the model’s default behavior to add explanations and social context.
  • Prompt is too broad or abstract: Vague terms like “efficiency” or “componentize” give creative freedom — which for LLMs, means chaos.
  • Prompt structure is inconsistent: Mixing bullet points with inline text often results in scrambled understanding. Stick to one format.

In stubborn cases, sometimes the best fix is: add a few-shot example. This means giving the model one or two prompt/output pairs before your real task, like this:

Here’s how I want things done:

Prompt:
Create a function that doubles each element

Output:
function doubleArray(arr) {
  return arr.map(x => x * 2);
}

Prompt:
Create a function that capitalizes first letter of each word

Output:
function capitalizeWords(str) {
  return str.replace(/\b\w/g, c => c.toUpperCase());
}

Prompt:
Create a function that returns Fibonacci sequence up to n:

Output:

With in-context examples, the model tends to mimic structure much better. It’s like giving it a warmup round before your actual request.

Overall, prompt failures usually trace to ambiguity, not stupidity.

Final thoughts on using AI to write code

To wrap up, building with AI code generation doesn’t mean you should give up testing, version control, or safety checks. It just means the input text — your prompt — needs to be treated like source code itself.

Test it, log it, reuse it, and version it.

And if you’re not sure whether your prompt is well-engineered, try handing it to a coworker and asking: “Will this always give me the same output, or could it surprise me?” That question will unlock most of the blind spots you didn’t know were in your input.