"Add validation to this function" leaves the important decisions unstated. A request that defines valid, invalid, and boundary inputs gives Copilot less room to guess and gives you something concrete to review. For code work, the prompt is the specification you use to grade the result.
Code prompts are specifications
The four-element structure you learned for everyday prompts (goal, context, source, expectations) carries straight into code, where it maps to four questions worth answering before you type:
- Goal: what behavior must change?
- Context: what constraints must the implementation preserve, and why is the work needed?
- Source: which files define the correct behavior, and what is each one authoritative for?
- Expectations: what output do you want, and what evidence will show it's correct?
Prompt length isn't the target. Remove the assumptions most likely to produce the wrong implementation. Instead of "Add validation to parseConfig," state that the function reads a user-editable config file, invalid data must raise an error the caller can handle, src/config.ts holds the implementation, and test/config.test.ts defines expected behavior. Ask for the validation rules first and require the public signature to stay unchanged. Now the request defines the change and the evidence for reviewing it.
Examples help when rules interact
A zero-shot prompt asks for the result with no worked example. It's the right first move when the requirements are explicit and the behavior is conventional (for example, implement normalizeEmail: trim, lowercase, reject an empty result, preserve the signature). Zero-shot is also a useful diagnostic: if the response exposes an unstated assumption, you've found a gap in your own spec.
A few-shot prompt includes one or more input/output examples. Reach for it when rules interact, when precedence matters, when output formatting is unusual, or when existing examples are more authoritative than any prose description. The examples do work that sentences struggle to: they show the exact result when two conditions are both true. One rule: your examples must agree with each other. If they imply conflicting behavior, resolve that conflict before you ask for code, or you'll hand Copilot a contradiction and get a coin-flip back.
This is where many plausible implementations go wrong. The structure looks reasonable, but the business rule is off. A decisive example makes that rule observable.
Fix a misunderstood rule with evidence
Say you asked for a discount calculation (members get 15%, non-members with 10+ items get 10%, discounts don't stack, member status wins) and Copilot returned code that adds both discounts for a member with a big order, returning 2,500 cents where the rule says 1,500. The tempting move is to start editing the implementation. Don't. Repair the specification first, because a vague spec keeps producing the same class of bug no matter how many times you patch the code.
Add one decisive example: { subtotalCents: 10000, isMember: true, itemCount: 10 } → 1500. Both conditions are true in that input, so the expected result exposes the precedence rule. Tell Copilot that member status wins and ask it to change only the rule-selection logic. Then run the member-only, bulk-only, both-conditions, and neither-condition cases. A focused follow-up keeps the correct parts of the implementation and repairs the rule that failed.
Watch for the subtler failure too: a parsing shortcut that looks like it enforces your contract. Ask for a port parser and a naive parseInt will accept "12px" or "1e3", passing your happy-path test while violating the spec. This is exactly why verification lives outside the chat: read the implementation, map each branch to a requirement, then run the awkward inputs ("80px", "1e3", " 3000 ") and compare against an expected table.
Chat history keeps these corrections cheap. You can change one rule without restating the whole specification, as long as you're explicit about what changed. "Keep the implementation, but member and bulk discounts must never stack. Apply the member rule first. Preserve the public API and all other behavior." is a one-line fix that reuses everything already correct. When a requirement changes, state its replacement along with the constraints still in force. Otherwise, an obsolete instruction from three messages ago can survive into the new answer.
A disagreement between the prompt and the tests needs a person to resolve it. Ask Copilot to report the exact input and the two expected results, then settle which source is authoritative before any code changes.
The Cookbook loop: a repeatable practice bank
GitHub's Copilot Cookbook collects recipes for tasks such as debugging invalid JSON, generating tests, refactoring for readability, and documenting legacy code. The useful part is the loop beneath the individual recipes. Run each task through six stages:
Select the recipe that matches the work product you need. Scope it to the exact files, issue, error log, or test, and record what must not change. Prompt with goal, context, source, and expectations. Inspect the response for assumptions the source doesn't support and confirm every named file and symbol exists. Iterate on one defect at a time: reinforce an error contract, cover a missing case, narrow a diff. Validate with evidence that fits the artifact: run the test, check the diff, or compare a non-code result against its source.
The loop gets reliable once two habits are in place. First, match the output to the validation method before you start: a debugging recipe ends in a reproduced-then-fixed test, a table recipe ends in a source-fidelity check where every cell traces to the input. Second, a filename mentioned in prose is not proof Copilot has the file's contents. Verify what's in context before you trust an answer built on it. Choose a recipe because it resembles the task in front of you. The goal is running the full loop well, not collecting recipes.