---
title: "12 — Prompt Engineering — Writing the Spec for a Probabilistic System"
uid: prompt-engineering
tags: ["prompt-engineering", "llm", "prompts", "ai", "roadmap:frontend"]
excerpt: "A prompt is a spec for a probabilistic system. Output quality is bounded by how unambiguously you stated intent — 'vague in, vague out' is the model working as designed."
date: 2026-08-12T18:35:10+0000
source: https://www.aveshina.my.id/en/blog/prompt-engineering
---

"Just talking to the model" was my prompt-engineering model, which made every bad output feel like a machine failure. The reframe that stuck: **a prompt is a spec for a probabilistic system.** An LLM doesn't look up answers — it guesses what comes next — so the quality of what comes back is capped by how clearly I described what I wanted [1]. "Vague in, vague out" isn't the model failing; it's the model guessing the most common version of an unclear request.

The image that clicked for me: treat a prompt like a brief you hand to a smart-but-literal junior. They're capable and know a lot, but they can't read your mind. If I don't spell out the tools, the tone, and the shape of the answer, they'll fill the gap with whatever answer is *most common* in everything they've read — not what I actually wanted. Good prompting is just writing that brief on purpose.

```figure
<svg viewBox="0 0 740 280" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="A prompt is a spec fed to a probabilistic system. Left box labelled BRIEF with five rows: role, context, constraints, examples, output format. A glowing arrow carries it into a circle labelled LLM — predicts plausible continuations. The LLM emits two response cards on the right: a hazy, drifting one labelled ambiguous prompt, and a sharp one labelled unambiguous prompt.">
  <defs>
    <marker id="parrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <!-- BRIEF box -->
    <rect x="30" y="50" width="210" height="180" rx="10" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="135" y="74" font-size="13" font-weight="700" fill="#1e1b4b" text-anchor="middle">BRIEF</text>
    <g font-family="ui-monospace, monospace" font-size="11" fill="#1e1b4b">
      <text x="48" y="100">role</text>
      <text x="48" y="126">context</text>
      <text x="48" y="152">constraints</text>
      <text x="48" y="178">examples</text>
      <text x="48" y="204">output format</text>
    </g>

    <!-- arrow to LLM -->
    <path d="M240,140 L360,140" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#parrow)"/>

    <!-- LLM orb -->
    <circle cx="415" cy="140" r="40" fill="#fef3c7" stroke="#d97706" stroke-width="1.5"/>
    <text x="415" y="138" font-size="13" font-weight="700" fill="#451a03" text-anchor="middle">LLM</text>
    <text x="415" y="154" font-size="9" fill="#451a03" text-anchor="middle">guesses the next word</text>

    <!-- two outputs -->
    <rect x="500" y="66" width="210" height="60" rx="8" fill="#fce7f3" stroke="#db2777" stroke-width="1.5" stroke-dasharray="4 3"/>
    <text x="605" y="92" font-size="12" font-weight="700" fill="#500724" text-anchor="middle">ambiguous prompt</text>
    <text x="605" y="110" font-size="10" fill="#500724" text-anchor="middle">hazy · drifts toward the average</text>

    <rect x="500" y="154" width="210" height="60" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="605" y="180" font-size="12" font-weight="700" fill="#052e16" text-anchor="middle">unambiguous prompt</text>
    <text x="605" y="198" font-size="10" fill="#052e16" text-anchor="middle">sharp · intent pinned down</text>

    <path d="M455,125 C475,115 490,100 498,96" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#parrow)"/>
    <path d="M455,155 C475,165 490,180 498,184" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#parrow)"/>

    <text x="370" y="262" font-size="11" fill="#64748b" text-anchor="middle" font-style="italic">same model — the brief decides the output</text>
  </g>
</svg>
```

## The model under the hood: predicting the next token

Before the techniques, the one fact that makes them all make sense. An LLM is, at its core, a next-token predictor — given the text so far, it assigns a chance to every possible next piece and picks one [2]. A token is just a small chunk of text, roughly a piece of a word. There's no step where the model goes and looks up the *real* answer; the reply is built piece by piece, each one chosen from a list of possibilities. That's why the same prompt can come back slightly different each run.

The thing I had to internalize: **every word in my prompt is a clue the model uses to guess.** My job is to pile the odds onto the kind of answer I want, instead of letting them spread across every possible reading. Every technique below — being specific, giving examples, fixing the format — is just a way to pile the odds in my favor.

## Zero-shot vs. few-shot: showing, not telling

The first fork in prompting is whether you give examples [3].

- **Zero-shot** — ask with no examples. "Translate to Indonesian: good morning." Works for common, well-trodden tasks where the model already has a strong habit to lean on. Cheap and fast.
- **Few-shot** — include a handful of input→output pairs before the real ask. The model infers the *pattern* from the examples and continues it.

Few-shot is what demystified the whole thing for me. Instead of *describing* a format in prose and hoping the model reads my description right, I just *show* the format a few times. The model is doing what it's built for — continuing a pattern — and a concrete example is far less ambiguous than a paragraph of instructions. It's the same instinct as reaching for a working test case instead of a spec document: the example is the spec.

The trade-off is length. Examples eat up the model's memory and cost money, and if they're too narrow they can pin the model too hard. I reach for zero-shot first; if the answer drifts, I add two or three examples before I touch the instructions.

## Being specific is the whole game

The failure I hit most often is leaving things unsaid. "Write a function to validate an email" sounds clear to me, but it leaves a dozen decisions to the model's habits: which language, which rules count as "valid," what to hand back when it's wrong, what about non-English email addresses, do we return a value or throw an error? Each of those is a guess the model makes by leaning on the average of its training.

The fix isn't more words — it's pinning down the decisions that matter:

- **Role** — "You are a senior TypeScript engineer." This sets how formal the answer sounds and how much it assumes I already know.
- **Context** — "This runs in a Next.js Server Component, so the browser isn't available." The model can't respect a limit it was never told about.
- **Constraints** — "Pure function, no outside libraries, throw on bad input, return a yes/no value." Each one closes off a whole direction the answer could wander.
- **Output format** — "Reply with only the code, no small talk, no markdown wrapping." Otherwise the model happily pads everything with a chatty explanation, because that's the most common shape of its training data.

I noticed these line up almost one-to-one with the sections of a good bug ticket or code review: who, where, what, in what shape. The skill carries over — prompt engineering rewards the same clear-thinking muscles as writing specs for people.

## Constraining the output

Telling the model *what shape* the answer should take is where I get the biggest payoff for the least effort. Without a format constraint, the model falls back to whatever is most common in its training — usually a friendly, padded explanation. For a coding task, that's noise.

Constraints I use all the time:

- "Return only valid JSON matching this exact shape." Then I write the shape down.
- "No preamble, no explanation. Code only."
- "Use exactly these headings: Problem, Approach, Trade-offs."
- "Maximum 3 sentences."

When the output is going to be read by code — like an LLM call inside a function — I'm strict about it. A stray "Here's your JSON:" at the start will break the parser that reads it. Format constraints aren't politeness; they're part of the contract between me and the model.

## The cost of ambiguity

The single idea that reframed everything: **ambiguity has a measurable cost.** Every decision I leave unsaid becomes a guess drawn from the model's habits, and each one is a chance for the answer to drift from what I meant. A prompt with five un-pinned decisions is rolling five dice; the result lands "plausible" on every one, but rarely "what I meant" on all of them at once [1][3].

This is why the same prompt gives inconsistent results across runs, and why two people writing "the same" prompt get different answers — they almost certainly didn't write the same prompt, because the things they assumed but didn't say differ. Tightening a prompt isn't about being polite to the model; it's about closing the gaps where my meaning could be read two ways. When a prompt "isn't working," my first move now is to hunt for the decision I left silent.

## How I use this

The habit these notes left me with is a single pause before I send: I read my own prompt back and ask, *what did I leave to the model's imagination?* If there's a role, context, constraint, example, or output format I assumed but didn't write, I add it. Most of the time the prompt that "didn't work" was just a brief with holes in it — and the model faithfully filled them with the average of everything I didn't say. Writing the spec on purpose, the way I'd write one for a person, is the whole skill.

## References

[1] learnprompting.org, "Introduction to Prompt Engineering," 2024. [Online]. Available: [https://learnprompting.org/courses/intro-to-prompt-engineering](https://learnprompting.org/courses/intro-to-prompt-engineering)

[2] IBM, "Prompt engineering techniques," IBM Think, 2024. [Online]. Available: [https://www.ibm.com/think/topics/prompt-engineering-techniques](https://www.ibm.com/think/topics/prompt-engineering-techniques)

[3] learnprompting.org, "Prompt Engineering," 2024. [Online]. Available: [https://learnprompting.org/](https://learnprompting.org/)

[4] roadmap.sh, "Prompt Engineering Roadmap," 2024. [Online]. Available: [https://roadmap.sh/prompt-engineering](https://roadmap.sh/prompt-engineering)

```quiz
Q: An LLM generates output by…
- looking up the single correct answer from a database
- guessing the next word by weighing chances, then picking one
correct: 1
explain: At its core an LLM is a next-token predictor — each word is chosen from a list of likely next words. There's no lookup step, which is why the same prompt can give different answers.

Q: Zero-shot prompting means…
- including several input→output examples before the real ask
- asking with no examples, relying on the model's habits
correct: 1
explain: Zero-shot gives the model no examples. Few-shot includes a handful of pairs so the model picks up the pattern by continuing it.

Q: Your output keeps coming back wrapped in chatty explanation. The cheapest fix is usually to…
- add more examples of friendly tone
- constrain the output format ("code only, no prose, no fences")
correct: 1
explain: Without a format constraint the model falls back to the most common shape in its training — a padded explanation. Pinning the output shape closes that off.

Q: Why does the "same" prompt often give inconsistent results across runs?
- The model randomly forgets parts of the prompt
- Every decision you left unsaid becomes a guess drawn from the model's habits
correct: 1
explain: Ambiguity has a cost. Each decision left silent becomes a fresh guess, so the answer lands "plausible" every time but rarely identical.

Q: Good prompt engineering mostly trains the same muscles as…
- writing a clear spec or bug ticket for a human
- memorizing a list of magic keywords
correct: 0
explain: Pinning down role, context, constraints, and output format maps onto who/where/what/in-what-shape — the same clarity that makes a good brief for a teammate.
```
