I was building a JSON extraction feature. The prompt was simple: read unstructured text, return structured JSON. It worked perfectly on my test input. Then I tried it on real data and it broke — the model wrapped the JSON in a paragraph, added fields I didn’t ask for, and occasionally returned YAML instead.
So I refined the prompt. And again. And again. By attempt 37, I had a prompt that worked reliably across hundreds of inputs. But more importantly, I had a framework — a repeatable structure for writing prompts that work the first time, not the thirty-seventh.
This post is that framework. Not theory. Not “top 10 tips.” The actual patterns, templates, and testing workflow I use every day after months of trial and error.
Related: If you need the foundation first, read How LLMs Actually Work — understanding the pipeline makes prompt engineering intuitive rather than guesswork.
Why Most Prompts Fail
The biggest mistake I see — and kept making myself — is treating a prompt like a conversation. “Summarize this for me.” “Can you extract the data?” “Make it shorter.”
That’s how you talk to a colleague who shares your context, your assumptions, and your mental model of “good output.” An LLM has none of that. It has exactly what you typed, and nothing else.
The prompts that fail are vague. The prompts that work are specs.
The Anatomy of a Prompt That Works
After dozens of iterations across different projects, I found that every reliable prompt contains four elements — always in this order:
1. Persona (Who are you?)
Tell the model who it should be. This frames its knowledge, tone, and behavior.
2. Goal (What do you want?)
A single, clear sentence. If you can’t state the goal in one sentence, you need to break the task into smaller prompts.
3. Constraints (What are the rules?)
Output format, length limits, what to include, what to avoid. This is where most prompts are too weak. Be explicit.
4. Examples (What does good look like?)
One or two examples of input → output. This is the single most effective technique for getting consistent results. The model pattern-matches against your examples far better than it follows abstract instructions.
Here’s what this looks like in practice:
You are a concise technical editor.Summarize the following meeting notes into exactly 4 bullet points.Each bullet must be under 20 words. Preserve all technical termsand proper nouns. Do not add commentary or interpretation.Example:Input: "We discussed migrating the auth service to OAuth2. Timelineis Q3. Sarah owns the migration. Risk: breaking mobile clients."Output:- Auth service migrating to OAuth2- Timeline: Q3- Owner: Sarah- Risk: potential breakage of mobile clientsNow summarize:<<<{input}>>>
When a prompt isn’t working, I debug by checking each element: Is the persona right? Is the goal clear? Are the constraints specific enough? Does the example actually show what I want?
Core Patterns — The Three You’ll Use 90% of the Time
Zero-Shot: Just Ask
No examples. Just a clear instruction. Works for tasks the model already does well.

When it works: Simple extraction, summarization, rephrasing, general knowledge.
When it breaks: Anything that requires a specific format the model hasn’t seen often, or nuanced judgment calls.
Few-Shot: Show, Don’t Tell
Provide 2-3 examples of input → output. The model pattern-matches against them.


When it works: Classification, structured extraction, any task where format consistency matters.
Why it’s powerful: The model doesn’t need to interpret your instructions — it sees the pattern and replicates it. I’ve found few-shot prompts are 3-5x more reliable than zero-shot for structured output.
System + User: Separation of Concerns
Most chat APIs support a system prompt (global instructions) and user prompt (the specific request). Use them like configuration vs. input:
- System prompt: Persona, constraints, output rules, things to avoid. Set once, applies to every message.
- User prompt: The specific task or question. Changes per request.


Why this matters in production: The system prompt is your configuration. The user prompt is your input. When output quality degrades, you know exactly where to look.
Templates I Actually Keep Open
These are the three templates I reach for most often. Each one was refined through dozens of iterations on real data.
Template 1: The Bulletproof JSON Extractor


The line “Do not output any text other than the JSON object itself” took me 12 iterations to discover. Without it, models wrap JSON in conversational fluff like “Sure, here’s the JSON you requested…” — which breaks every parser downstream.
Template 2: The Step-by-Step Processor


This is my alternative to “chain-of-thought” prompting. Instead of asking the model to “think step by step” (which produces verbose, unpredictable reasoning), I tell it exactly which steps to take. More deterministic. More testable.
Template 3: The Format Converter



The “If any section has no content, write ‘None’” instruction prevents the model from inventing content to fill empty sections — a common failure mode I learned the hard way.
Advanced Tactics for When Simple Prompts Aren’t Enough
Temperature: Your Consistency Dial
Temperature controls randomness. This isn’t a minor setting — it’s the difference between reliable and chaotic:
| Temperature | Behavior | Use for |
|---|---|---|
| 0.0 | Always picks the highest probability token | JSON extraction, classification, code generation |
| 0.3-0.5 | Mostly consistent with slight variation | Summarization, rewriting |
| 0.7-1.0 | Creative, varied, occasionally surprising | Brainstorming, creative writing, ideation |
My default: 0.0 for any structured output, 0.7 for anything creative. I only go higher than 0.7 for brainstorming where I explicitly want wild ideas.
The Self-Correction Loop
For critical tasks, don’t trust the output — validate and retry:
response = llm.generate(prompt)try: result = json.loads(response)except json.JSONDecodeError as e: # Send the error back to the model correction_prompt = f""" The JSON you provided was invalid. Error: {e} Original response: {response} Please return only valid JSON. """ result = json.loads(llm.generate(correction_prompt))
This self-correction loop catches ~95% of format failures in my experience. The model is surprisingly good at fixing its own mistakes when you tell it exactly what went wrong.
Prompt Chaining: Small Prompts > Big Prompts
When a task is complex, don’t build one massive prompt. Chain smaller, focused prompts:
Prompt 1: "Extract all product names from this text" → ["Milk", "Bread", "Yoghurt"]Prompt 2: "For each product, classify as [Dairy, Bakery, Other]" → [{"name": "Milk", "category": "Dairy"}, ...]Prompt 3: "Generate a markdown summary table from this data" → | Product | Category | ...
Each prompt does one thing. Each output is verifiable before it feeds into the next step. When something breaks, you know exactly which link in the chain failed.
How I Test Prompts — The Checklist
I treat prompts like code. They get tested before they ship.
1. Consistency test: Run the same prompt 10 times with temperature 0. If outputs vary meaningfully, the prompt is ambiguous. Add constraints or examples.
2. Edge cases: Test with empty input, very long input, input in a different language, garbage text. The prompt should fail gracefully, not hallucinate.
3. Format validation: If you expect JSON, parse it programmatically on every test run. Set a threshold: if parsing fails more than 1% of the time, the prompt needs work.
4. Adversarial inputs: Try inputs that could confuse the model — text that contains instructions (“ignore the above”), conflicting information, or content that looks like the prompt template itself.
5. Human review: For user-facing output, review 20-50 samples manually. Check for correctness, tone, subtle biases, and hallucinated details.
The Mistakes I Stopped Making
After months of prompt engineering, these are the habits I broke:
“Make it shorter” → Now I say “keep it under 50 words.” Measurable constraints beat vague adjectives.
“Be helpful and professional” → Now I say “You are a senior technical writer for an engineering blog. Use active voice. No marketing jargon.” Specific personas beat generic adjectives.
One mega-prompt for everything → Now I chain 2-3 focused prompts. Each one does one thing well. Easier to debug, easier to maintain, better results.
Assuming the model remembers → Now I include all necessary context in every prompt. The model doesn’t have institutional knowledge. If it needs a rule, I state it explicitly — every time.
Skipping examples → Now I always include at least one few-shot example for structured output. It takes 30 seconds to add and saves hours of debugging inconsistent formats.
Final Thoughts: Structure Beats Cleverness
Prompt engineering is not about clever tricks or magic words. It’s about clear communication with a system that takes you exactly at your word.
The framework is simple: Persona → Goal → Constraints → Examples. The testing is simple: run it 10 times, check the edges, validate the format. The debugging is simple: which of the four elements is too weak?
If you remember one thing from this post: the model has no context beyond what you give it. Every assumption you leave unstated is a failure mode waiting to happen. State everything. Show examples. Test like it’s code.
Because in production, it is code.
Previously: I Traced a Single Prompt Through an LLM. Next: Prompt Injection Explained: Attacks, Examples, and Defenses.

Leave a Reply