Prompting Patterns That Get Great Code
Prompting Patterns That Get Great Code
The quality of what Copilot produces is almost entirely determined by what you give it. The same model, given a vague prompt, produces vague code. Given a precise, well-structured prompt, it produces production-quality code. This lesson is a practical guide to prompting well.
Pattern 1: The Specification Comment
Before you write a function, write a detailed comment that specifies exactly what it should do. Treat it like a mini-spec:
python# Merge two sorted lists into a single sorted list. # Both input lists may be empty. # Do not modify the input lists. # Time complexity should be O(m + n) where m and n are the lengths of the inputs. # Return a new list. def merge_sorted(a: list[int], b: list[int]) -> list[int]:
After this, press Enter. The model has everything it needs: input types, output type, behavior for edge cases (empty lists), and a complexity constraint.
Compare to just typing def merge_sorted(a, b): — the model would guess at types, might modify the inputs, and would not know about the complexity requirement.
Pattern 2: Show, Then Ask
If you want code that follows a specific style, show an example first:
typescript// Existing code in the file: async function getUser(id: string): Promise<Result<User, AppError>> { try { const user = await userRepo.findById(id); if (!user) return err(new NotFoundError(`User ${id} not found`)); return ok(user); } catch (e) { return err(new DatabaseError("getUser failed", e)); } } // Now type: // Same pattern as getUser, but fetch a product by its SKU async function getProductBySku(sku: string): Promise<Result<Product, AppError>> {
Copilot will follow the exact same Result<T, E> pattern, the same error types, and the same structure because you showed it what the pattern looks like in your codebase.
Pattern 3: Constrain the Solution Space
When you do not constrain, the model chooses freely — sometimes picking patterns you did not want. Add constraints explicitly:
javascript// Parse the CSV string and return an array of objects. // - Use only the standard library (no csv-parse or papaparse) // - Handle quoted fields that contain commas // - First row is always the header // - Return empty array if input is empty string // - TypeScript, strict mode function parseCsv(input: string): Record<string, string>[] {
Without the "no external library" constraint, Copilot might import csv-parse. Without "first row is header", it might treat headers as data.
Pattern 4: Write the Test First
Writing the test before the implementation is a powerful prompting technique. The test is a precise, executable specification:
pythondef test_calculate_discount(): # 10% discount for orders over $100 assert calculate_discount(150.0) == 15.0 # No discount for orders under $100 assert calculate_discount(80.0) == 0.0 # Exactly $100 gets no discount (strictly greater than) assert calculate_discount(100.0) == 0.0 # Discount caps at $50 assert calculate_discount(1000.0) == 50.0
Now type
def calculate_discount(amount: float) -> float:Pattern 5: The Incremental Build
Never ask for a large feature in one shot. Break it into steps, verify each one:
Step 1:
code// Step 1: Define the TypeScript interface for a shopping cart item // Fields: productId (string), quantity (number), unitPrice (number)
Accept and verify.
Step 2:
code// Step 2: Function to calculate the total price of a cart // Input: CartItem[] // Output: number (sum of quantity * unitPrice for each item) // Round to 2 decimal places
Accept and verify.
Step 3:
code// Step 3: Function to apply a discount code to a cart total // Discount codes: "SAVE10" = 10%, "SAVE20" = 20%, "FLAT5" = $5 off // Invalid codes return the original total unchanged
Each step is small enough to read and verify in 30 seconds.
Pattern 6: Paste the Error
When debugging, do not just ask "fix this". Paste the exact error:
python# This function is raising: # TypeError: '<' not supported between instances of 'str' and 'int' # on line 12 when processing mixed-type lists. # Fix it to handle both string and int elements by converting to string before comparing. def find_min(items):
The error message plus the fix direction gives Copilot exactly what it needs.
Pattern 7: Reference the Docs
For APIs or libraries Copilot might not know well, paste the relevant documentation snippet:
typescript// According to the Stripe API docs: // stripe.paymentIntents.create({ // amount: number (in smallest currency unit, e.g. cents), // currency: string (e.g. "usd"), // automatic_payment_methods: { enabled: true } // }) // Returns a PaymentIntent object with client_secret. // Create a payment intent for the given order amount in USD async function createPaymentIntent(amountInDollars: number) {
By including the relevant API signature in the comment, you prevent Copilot from inventing parameters.
What makes prompts fail
| Problem | Fix |
|---|---|
| No type information | Add types to the comment or signature |
| No edge case mention | List edge cases explicitly: "empty input", "null", "negative numbers" |
| No pattern reference | Open a similar file in another tab or paste an example |
| Too broad | Break into smaller steps |
| No constraints | Explicitly state what not to use or do |
| Outdated API | Paste the relevant docs section into the comment |