Prompting Guide
The principles presented in this document apply broadly regardless of which agent or interface you are using. This document lays out the most common building blocks — that is, strategies and prompt ingredients — used to harness model capabilities. See the When to use these building blocks section to get an overview of how you can mix and match these components in your prompting workflow.
For guidance on agentic coding workflows, working with specific tools like Claude Code, Codex or Cursor, and structuring multi-agent tasks, see Tips and Lessons Learned.
Writing Clear Instructions
Define the scope explicitly. Do not use open-ended instructions like “clean up this function.” Specify exactly what should change and what should not.
Refactor the `processPayment` function below to remove the redundant null check on line 14. Do notchange variable names, restructure the logic, or modify any surrounding code.Use precise technical language. Write the way you would in a code review: “extract this into a pure function,” “replace this with an async/await pattern,” “add a covering index to this query.” Avoid vague terms like “make this faster” or “make this cleaner.”
State the language and framework version explicitly. Do not rely on the model to infer it from pasted code. This matters most in fast-moving ecosystems: React hooks vs. class components, NestJS v9 vs. v10 module registration, Python 3.10 vs. 3.12.
Using TypeScript with React 18 and the React Query v5 API, write a custom hook that fetches a userprofile by ID and handles loading, error, and success states.Write positive instructions, not just negative ones. Reserve negative constraints for hard rules that must never appear in the output.
Don’t do this:
Don't use any third-party libraries.Do this:
Implement this using only the Node.js standard library. Do not import any external packages.Don’t do this:
Make this API faster and cleaner.Do this:
Replace the sequential database calls on lines 22-30 with a single batch query. Use the existing`batchFetch` method in db/queries.ts. Do not change the response shape.Provide Context
Share the surrounding code, not just the target. If you are modifying a function, include the functions that call it and the ones it calls. If you are writing a new component, paste an existing one so the model can match your conventions.
State your conventions explicitly. Do not assume the model will infer your patterns from one or two examples. Write out your error handling approach, service class structure, or database naming conventions.
We follow the repository pattern for data access. All database querieslive in a dedicated `*Repository` class. Service classes callrepositories and never query the database directly. Write a`UserRepository` class following this pattern with the methods below.For architecture and design tasks, provide a brief system summary. Include your layers, persistence mechanism, and relevant constraints like latency requirements or deployment environment. A short description is enough.
Don’t do this:
Write a UserRepository class with findById, save, and delete methods.Do this:
We use the repository pattern: all database queries live in `*Repository` classes, and servicesnever query the database directly. Write a `UserRepository` class with findById, save, and deletemethods. Here is our existing `OrderRepository` for reference:
[paste OrderRepository code]Specify the Output
Specify the format explicitly. State whether you want a complete file, a single function, a diff, a JSON schema, or a prose explanation. If you don’t specify what you want the model to output, then it will make a decision for you, and it won’t always be what decision that you want. The model can’t always tell what you prefer unless you say so.
State what to include and exclude. Specify whether you want imports, type annotations, docstrings, inline comments, or test cases. Do not leave these to the model’s defaults.
Define error handling expectations precisely. Do not write “handle errors gracefully.” Write exactly what should happen.
Return only the updated function, no imports, no surrounding class definition, no explanation.Include a Google style docstring. Add inline comments only where the logic is non-obvious. Raise aValueError with the message "user_id must not be None" if user_id is None.Don’t do this:
Write a function to validate email addresses.Do this:
Write a TypeScript function `validateEmail(input: string): boolean`. Return only the function, noimports, no class wrapper. Include a JSDoc comment. Throw if the input is not a string.Testing and Iteration
Test against at least five to ten diverse inputs before deploying. Include edge cases: null inputs, empty arrays, unexpected response shapes. Do not test only the happy path.
Change one variable at a time. Changing wording, context, and output spec simultaneously makes it impossible to identify what improved the output.
Build a regression suite for production prompts. Maintain canonical inputs with known-good outputs. Run new versions against this suite before deploying and after every model upgrade.
Watch for silent regressions. A change that fixes one case can quietly break another. Your test suite needs to cover your real input distribution.
Don’t do this:
The output is wrong. I rewrote the instructions, added more examples, and switched to a differentmodel. It still doesn't work.Do this:
The function handles the happy path but returns undefined for empty arrays. Keep everything else thesame and add a guard clause for empty input.Advanced Techniques
The following techniques ought to be used when clear natural language isn’t sufficient to communicate complex ideas to the model. Try a combination of the techniques mentioned previously and only fallback to these techniques if your use case genuinely warrants them.
Few-Shot Examples
Use few-shot prompting when the desired output follows a pattern that is hard to describe but easy to demonstrate. Good candidates include commit messages in your team’s format, test cases following your naming conventions, and code review comments at a specific level of detail.
Provide two to five diverse examples covering different cases. Do not provide multiple examples that illustrate the same point.
Here are two examples of how we write unit tests in this codebase. Follow the same structure andnaming conventions for the new tests.
Example 1:[paste example test]
Example 2:[paste example test]
Now write tests for the `calculateDiscount` function below:[paste function]Don’t do this:
Write a description for POST /invoices.Do this:
Here are examples of endpoint descriptions:
1. GET /users?role={role} — "Returns a paginated list of users filtered by role. Defaults to all roles. Max 100 per page."2. POST /orders — "Creates an order from the items in the authenticated user's cart. Returns 409 if inventory is insufficient."3. DELETE /sessions/{id} — "Revokes the session and invalidates its refresh token. Returns 204 on success."
Write a description for POST /invoices.Reasoning and Chain-of-Thought
Use chain-of-thought for tasks requiring multi-step analysis: debugging complex issues, evaluating architectural tradeoffs, analyzing security implications, or assessing query performance.
Before suggesting a fix, reason through what this stack trace tells us about the application stateat the time of the crash. Then propose a fix based on that reasoning.Do not use it for straightforward generation tasks like writing a CRUD endpoint. It adds latency without improving output quality on tasks that do not require multi-step reasoning.
Don’t do this:
Think step by step about how to write a REST endpoint that creates a user.Do this:
This query times out on tables with over 1M rows. Walk through the execution plan, identify whichjoins or scans cause the slowdown, then suggest index changes.Older Techniques
These were commonly recommended in earlier generations of LLM tooling. Current models have largely made them unnecessary as general practice.
Role Prompting
Role prompting means prefacing a task with an explicit identity: “You are a senior backend engineer” or “You are a security auditor.” The idea was that a persona would calibrate the model’s expertise.
Current models have enough built-in domain knowledge that this rarely changes output quality. Asking the model to “review this code for security vulnerabilities” is just as effective as first assigning it a role. If you want a specific perspective, describe the desired behavior directly.
Don’t do this:
You are a world-class senior staff engineer with 20 years of experience in distributed systems,cloud architecture, and performance optimization. Review this code.Do this:
Review this code for connection pool exhaustion, unbounded retries, and missing circuit breakers.XML Tags
XML tags were broadly recommended to help models distinguish instructions from context and data. Current models infer that from natural language without explicit markup. If a prompt feels ambiguous, rewrite the language rather than wrapping it in tags.
Two situations still justify their use. The first is prompt injection defense: when injecting user-supplied content into a prompt template programmatically, wrap it in a named tag to signal it is data, not an instruction.
<task>Review the code below for security vulnerabilities.</task>
<user_submitted_code>{{untrusted_code}}</user_submitted_code>The second is complex multi-document prompts where a single call must reason across several distinct artifacts simultaneously.
<reference_documents> <document index="1"> <source>Current Implementation</source> <content>{{implementation}}</content> </document> <document index="2"> <source>Failing Test Suite</source> <content>{{tests}}</content> </document></reference_documents>
<task>Identify why the tests are failing and propose a fix.</task>Outside of those two cases, do not use XML tags as a general formatting habit.
When to use these building blocks
Include the components below as the task requires. Not every prompt needs all of them.
| Prompting Component | Purpose | Required? |
|---|---|---|
| Clear Instructions | Remove ambiguity by defining exact scope, language, and constraints for the task | Always |
| Provide Context | Give the model surrounding code, conventions, and architecture so it matches your codebase | Almost always |
| Specify the Output | Control the format, structure, and content of the response so you get usable output on first try | Recommended |
| Few-Shot Examples | Show two to five input/output pairs when the pattern is easier to demonstrate than describe | For complex tasks |
| Role / Persona | Assign an explicit identity when you need a specific perspective, such as security auditor | Situational |
| XML Tags | Separate untrusted data from instructions or distinguish multiple documents in a single prompt | Situational |
Common Pitfalls
Do not paste code without explaining the goal. “Fix this code” with a stack trace is one of the weakest prompt patterns. State what you want: the minimal fix, the robust solution, or the refactor.
Do not omit surrounding context. A function written in isolation almost always needs a second pass to integrate. Share the relevant surrounding code upfront.
Do not use vague scope instructions. “Improve this” and “refactor this” invite the model to decide what improvement means. Specify what should change and what should not.
Do not assume the model knows your framework’s current API. Specify the version, especially in fast-moving ecosystems like NestJS, Next.js, or LangChain.
Do not treat a working prompt as permanent. Review production prompts regularly and tie that review to your model upgrade process. Output quality can shift across model versions even when the prompt has not changed.