Skip to content

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 not
change 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 user
profile 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 queries
live in a dedicated `*Repository` class. Service classes call
repositories 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 services
never query the database directly. Write a `UserRepository` class with findById, save, and delete
methods. 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 a
ValueError 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, no
imports, 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 different
model. It still doesn't work.

Do this:

The function handles the happy path but returns undefined for empty arrays. Keep everything else the
same 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 and
naming 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 state
at 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 which
joins 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 ComponentPurposeRequired?
Clear InstructionsRemove ambiguity by defining exact scope, language, and constraints for the taskAlways
Provide ContextGive the model surrounding code, conventions, and architecture so it matches your codebaseAlmost always
Specify the OutputControl the format, structure, and content of the response so you get usable output on first tryRecommended
Few-Shot ExamplesShow two to five input/output pairs when the pattern is easier to demonstrate than describeFor complex tasks
Role / PersonaAssign an explicit identity when you need a specific perspective, such as security auditorSituational
XML TagsSeparate untrusted data from instructions or distinguish multiple documents in a single promptSituational

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.