Getting Started with LLMs: Prompt Engineering Basics
Prompt engineering isn't magic phrases, it's giving a model the context a human collaborator would need. The mental model, the techniques, where they break.
Ask ten developers what prompt engineering means and eight will describe it as collecting magic phrases: say "think step by step" here, add "you are an expert" there, and the model somehow gets smarter. That framing survives because it occasionally works, which is worse than if it never worked at all. It teaches people to copy incantations instead of understanding why some prompts succeed and others quietly fail.
Here's the model that actually holds up: a large language model answers with whatever is most probable given the text you gave it. It has no access to your intentions, your codebase, or the conversation in your head before you started typing. Every ambiguity you leave in the prompt, the model resolves by guessing, and it guesses using the statistically common case, not your specific case. Prompt engineering is the discipline of removing that ambiguity before it costs you a bad answer, the same way a good bug report removes ambiguity for a human engineer reading it cold.
Why "write code" fails and specificity doesn't
Compare two prompts:
Write code
Write a Python function that validates email addresses using regex.
Include docstrings, handle None input without raising, and return
True/False. Show one example call.
The first prompt has no wrong answer, which means it also has no right one. The model has to invent a language, a problem, and a scope, and it will invent the most statistically common combination in its training data, which is unlikely to be what you actually needed. The second prompt closes off decisions the model would otherwise have to guess at: language, technique, edge case behavior, and return type are all specified. What's left for the model to do is the part you actually wanted help with, the implementation.
This is the whole game, more than any specific phrase: a prompt's job is to constrain the space of acceptable answers down to the one you want. Vague prompts are not friendlier or more natural, they just move the ambiguity from your keyboard to the model's guess, and guesses are wrong more often than instructions are.
Context is not optional, it's the input
A common mistake is treating a prompt like a search query, a few keywords and hope. Compare:
Translate this code
I'm porting a Python Flask authentication endpoint to Express.js
middleware for a Node backend. Keep the same session validation
logic and error responses; only the framework should change.
[code here]
Without context, "translate" is underspecified in a way that matters: should it preserve exact behavior, or write idiomatic Express regardless of the original logic? Should it keep the same error messages? A model without your context will pick a reasonable-looking default that has maybe a 50% chance of matching what you needed, and you won't find out which half you got until you read the diff carefully. The fix costs you three extra sentences and saves you a review cycle.
The general rule: include anything a competent human collaborator would need to ask before starting the work. If you'd expect a coworker to ask "translate for what, preserving what behavior?", the model needed that answer too, it just won't ask, it will assume.
Examples do more work than description
Language models are pattern completers at their core, which means showing them the pattern is often more effective than describing it. This is called few-shot prompting, and it's worth understanding why it works rather than just using it as a trick.
Classify the following reviews as positive or negative:
Review: "Best product ever, highly recommend!"
Classification: positive
Review: "Waste of money, broke after one day"
Classification: negative
Review: "It's fine, does what it says, nothing special"
Classification: ?
Two labeled examples establish the format, the granularity of judgment, and implicitly the edge cases (note the third review is genuinely ambiguous, a mixed review, which is exactly the case an instruction like "classify as positive or negative" leaves undefined). If you only described the task in words, the model would have to infer the output format from your description alone; showing it two input-output pairs makes the format unambiguous and gives it a reference point for tone and confidence.
The trade-off is token cost. Every example you add is text the model has to process on every single call, and for high-volume applications that adds real latency and API cost. A rule of thumb: use zero examples for tasks the model handles reliably out of the box, one or two for tasks where format matters, and reserve three or more for genuinely idiosyncratic formats, at which point you might be better served by a fine-tuned model or a strict output schema than by stuffing more examples into every prompt.
Chain of thought: buying accuracy with tokens
For problems that require several logical steps, arithmetic, multi-step reasoning, debugging a stack trace, asking the model to show its work measurably improves accuracy. This isn't superstition; it has a real mechanical explanation. A model generates one token at a time, and each token it produces becomes part of the context for the next one. If you force it to write out intermediate steps, those steps become scratch space the model can build on, the same way you'd trust your own arithmetic more if you wrote it out than if you tried to do it silently in your head and blurted the final number.
Solve this step-by-step:
If a train travels at 60 mph for 2 hours, then 80 mph for 1 hour,
what's the average speed?
Show: total distance, total time, then the average speed formula,
then the final answer.
Compare that to just asking "what's the average speed", which invites the model to jump straight to a plausible-sounding number, sometimes the wrong one, because it never separated distance and time. The explicit steps aren't decoration, they're the mechanism.
The cost side of this trade-off is real: chain-of-thought responses are longer, which means slower and more expensive per call. For a simple factual lookup, forcing step-by-step reasoning is pure overhead. Reserve it for problems that genuinely have multiple steps where an error in step two would corrupt everything after it: math, logic puzzles, multi-file code changes, debugging.
Role prompting: useful, but not a cheat code
Telling a model "you are a senior security engineer reviewing this code" does change its output, measurably, because it shifts what kind of response is statistically associated with that framing in its training data: more focus on vulnerabilities, more caution about edge cases, different vocabulary.
You are an experienced security engineer reviewing this pull request.
Focus specifically on injection vulnerabilities, authentication
bypasses, and unsafe deserialization. Ignore style issues entirely.
[code here]
What role prompting cannot do is grant the model actual expertise it lacks, or make it check facts it has no way to check. It's a framing tool, it steers tone and focus, not a knowledge injector. Pair it with a specific scope, as in the example above ("focus specifically on X, ignore Y"), because the role alone is vague enough that the model will still guess at what you want reviewed.
Output format: say it, don't hope for it
If you need JSON, a table, or a specific structure back, say so explicitly and show the shape:
Generate 5 JavaScript naming convention tips.
Return as a JSON array of objects, each with "tip" and "example" keys.
Leaving format unstated is a common source of frustration with programmatic LLM use: the model returns a nicely formatted answer for a human reader, then your code fails to parse it because it wrapped the JSON in a sentence or added markdown fences you didn't expect. Many modern APIs, including Anthropic's and OpenAI's, now offer structured output modes that constrain the response to a schema at the API level rather than relying on the prompt alone; if your use case is programmatic, prefer that mechanism over prompt instructions whenever it's available, since a schema constraint can't be argued out of the way, but a prompt instruction sometimes can.
Iterate like you'd debug, not like you'd guess
The biggest mindset shift for people new to this: your first prompt is a draft, not a submission. Treat prompt development the way you'd treat writing a function, write a version, test it against real inputs including weird ones, notice where it breaks, and adjust.
prompts = [
"Write a recursive fibonacci function",
"Write an efficient recursive fibonacci with memoization",
"Write a recursive fibonacci function in Python with memoization "
"that handles negative inputs by raising ValueError",
]
for prompt in prompts:
result = call_llm(prompt)
evaluate(result) # check correctness, edge cases, style
Each version in that list exists because the previous one revealed a gap: the first didn't specify efficiency, so you might get the exponential-time version; the second didn't specify what happens on invalid input, so behavior there is undefined. That's the actual skill, not memorizing a phrase list, but noticing what a given output got wrong and translating that gap into a constraint for next time.
This matters even more once code enters the picture, because a plausible-looking function is not the same thing as a correct one, and the review habits you build for AI output in general apply directly here; see our guide on reviewing AI-generated code for the checklist.
Where prompting alone runs out of road
It's worth being honest about the limits. No amount of prompt cleverness will make a model reliably do arithmetic on numbers larger than it can represent as tokens cleanly, or recall a fact it was never trained on, or stay perfectly consistent across a very long conversation where earlier instructions scroll out of the effective context the model attends to strongly. For those problems, the fix isn't a better prompt, it's a different architecture: giving the model a calculator tool, retrieving real documents instead of relying on memorized facts, or re-stating critical instructions periodically in long sessions. Recognizing when you've hit a prompting ceiling, rather than writing an increasingly baroque prompt to work around it, is itself part of the skill.
Prompt engineering, done well, is applied clarity: say exactly what you mean, show the shape of the answer you want, give the model room to show its reasoning when the problem needs it, and verify what comes back instead of trusting it because it sounds confident. None of that requires a phrase list. It requires treating the model as what it is, a powerful but literal collaborator that only knows what you told it.
Next step: the AI Developer path on Kodion walks through this hands-on, building real prompts against real tasks with instant feedback on what works and why.
Kodion Team
Kodion Editorial
Written by the team that builds Kodion's courses: working engineers who test every example in the interactive editor before it ships. How we create and review content
Continue Learning
📚 Related Articles
Why AI Coding Tools Hallucinate APIs (and How to Catch It)
Why AI assistants invent functions, parameters, and whole packages that do not exist, the four flavors of code hallucination, and a workflow to catch them.
How to Build an App With AI When You Can't Code (A No-Hype Guide)
Vibe coding looks like magic until minute 41. Here's what actually happens after the demo, and the four skills that get a non-coder to a live app.