GitHub Copilot 101Intermediate15 min

Prompting for Code: Patterns and Recipes

A code prompt is a testable specification. Learn to write goal, context, source, and expectations for code, use few-shot examples to pin down precedence, and run a repeatable Cookbook loop that ends in verification.


What you'll learn

  • Write a code prompt as a testable specification
  • Choose between zero-shot and few-shot prompting
  • Use a few-shot example to resolve interacting rules and precedence
  • Run the Select–Scope–Prompt–Inspect–Iterate–Validate loop on a real task
On this page

"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:

  1. Goal: what behavior must change?
  2. Context: what constraints must the implementation preserve, and why is the work needed?
  3. Source: which files define the correct behavior, and what is each one authoritative for?
  4. 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.

A code prompt with all four elements· github-copilot
Bad example

Add validation to parseConfig and update the tests. Keep the code clean.

Good example

Goal: Add validation to parseConfig. Context: it reads a user-editable config file, so invalid data must raise an error the caller can handle. Preserve the public signature. Source: use src/config.ts for the implementation, src/types.ts for the public type, and test/config.test.ts as the behavioral authority. Expectations: list the validation rules first, then propose the smallest change and the missing tests. Show the diff.

Why this works: A rules list, a minimal implementation, and focused tests, scoped to the named files, with the signature preserved so you can review a small, checkable diff.

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.

Use a few-shot example to settle precedence· github-copilot
Bad example

Fix discountCents so members get the right discount and discounts do not stack.

Good example

Revise discountCents using these authoritative examples. Input { subtotalCents: 10000, isMember: true, itemCount: 2 } → 1500. Input { subtotalCents: 10000, isMember: false, itemCount: 10 } → 1000. Input { subtotalCents: 10000, isMember: true, itemCount: 10 } → 1500. The third example proves member and bulk discounts do not stack and member status wins. Preserve the type and change only the rule-selection logic.

Why this works: An implementation where the member rule takes precedence and discounts never stack. The decisive third example turns an ambiguous sentence into a checkable result.

Name why each source is authoritative

"Use src/config.ts" names the file while leaving its role unclear. Say whether a file is the current implementation, the public type, the error convention, or the behavioral authority. Naming authority is what lets Copilot resolve a disagreement instead of guessing which one to trust.

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.

A scoped debugging recipe prompt· github-copilot
Bad example

Fix the failing escaped-quote test in the webhook parser without breaking anything else.

Good example

Goal: diagnose why the escaped-quote test fails and propose the smallest safe fix. Context: this TypeScript parser processes webhook JSON. The public signature must not change and malformed input must still throw ParseError. Source: use src/parseWebhook.ts and tests/parseWebhook.test.ts and treat the test expectations as requirements. Expectations: explain the cause, propose a minimal change, preserve the ParseError contract, and don't rewrite unrelated code.

Why this works: A cause, a minimal patch, and a preserved error contract. Validate it by rerunning the previously failing test and confirming malformed input still throws.

Try it yourself

Turn a vague prompt into a verified port parser

Practice the zero-shot to few-shot progression on a function with tricky edge cases. Budget about eight minutes.

  1. 01

    In a scratch TypeScript file, add a parsePort(value: string | number) stub that throws. Write down the contract: return an integer 1–65535. Reject empty strings, decimals, signed strings, exponent notation, non-digit suffixes, and out-of-range values.

  2. 02

    Send a zero-shot prompt using only the contract, and save Copilot's first implementation.

  3. 03

    Test it against the tricky rows: "80px", "1e3", " 3000 ", "00080", "-1". Note every case it gets wrong.

    Hint: Naive numeric conversions often accept "12px" or "1e3".

  4. 04

    Rewrite the prompt with goal, context, source, and expectations, and include at least the "80px", "1e3", and " 3000 " examples as authoritative.

  5. 05

    Request a corrected implementation, then rerun every case and confirm each returns the expected value or the exact error.

A port parser that survives its awkward inputs, and a clear feel for when a few-shot example does what a paragraph of prose can't.

Key takeaways

  • A code prompt is a testable specification. Goal, context, source, and expectations expose hidden assumptions.
  • Use zero-shot when requirements are explicit. Use few-shot when rules interact or precedence matters.
  • A single decisive example makes an ambiguous rule observable, so repair the spec before patching the code.
  • Name why each source is authoritative rather than assuming an open file is the source of truth.
  • Run the Select–Scope–Prompt–Inspect–Iterate–Validate loop and verify outside the chat.

Check your understanding

  1. 1. Which is the more assessable way to prompt "Add validation to parseConfig"?

  2. 2. When should you switch from zero-shot to few-shot prompting?

  3. 3. Copilot's discount code stacks member and bulk discounts, but the rule is member-precedence, no stacking. What should you repair first?

  4. 4. You tell Copilot to "use src/config.ts." Why add why the file matters?

  5. 5. In the Cookbook loop, you've prompted and Copilot proposed a fix. What completes the loop?

  6. 6. A generated parser passes your one happy-path test but accepts "12px" as valid. What does this teach?

Frequently asked questions

Terms used in this lesson

zero-shot prompt
A prompt that asks Copilot to perform a task without providing a worked input/output example.
few-shot prompt
A prompt that includes one or more input/output examples, used to clarify interacting rules, precedence, or unusual formatting.
behavioral contract
The written statement of accepted inputs, outputs, errors, and constraints that a code prompt turns into a checklist for review.
source authority
The stated role of a referenced file (implementation, public type, or behavioral authority) that tells Copilot which source to trust.

Further reading