The difference between a bad prompt and a good prompt is the difference between a useless answer and a perfect one. Prompt engineering is how you tell Claude exactly what you want — and get it.
This is Article 5 in the Claude AI — From Zero to Power User series. You should have completed Article 2: Getting Started before this article.
By the end of this article, you will know five practical techniques for writing better prompts.
What is a System Prompt?
The Claude API has three message roles:
- system — Instructions that set Claude’s behavior for the entire conversation
- user — Your messages (the questions and tasks)
- assistant — Claude’s responses
The system prompt is special. It is not part of the message history. It sits above the conversation and applies to every response Claude gives.
Python
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="You are a senior Python developer. Always include type hints in your code. Use docstrings for all functions.",
messages=[
{"role": "user", "content": "Write a function to calculate compound interest"}
]
)
print(message.content[0].text)
TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: "You are a senior Python developer. Always include type hints in your code. Use docstrings for all functions.",
messages: [
{ role: "user", content: "Write a function to calculate compound interest" }
],
});
if (message.content[0].type === "text") {
console.log(message.content[0].text);
}
Without the system prompt, Claude writes generic code. With the system prompt, Claude includes type hints and docstrings in every response. The system prompt shapes all of Claude’s behavior.
Writing Effective System Prompts
A good system prompt has three parts:
- Role — Who Claude should be (one line)
- Rules — What Claude should always or never do (bullet points)
- Output format — How the response should look
Example: Code Review Prompt
system_prompt = """You are a senior code reviewer.
Rules:
- Focus on bugs, security issues, and performance problems
- Ignore style preferences (formatting, naming conventions)
- Rate severity as: critical, warning, or suggestion
- If the code is good, say so. Do not invent problems
Output format:
- List each issue with: file, line, severity, description, and fix
- End with a summary: total issues found by severity
"""
This prompt is clear, structured, and tells Claude exactly what to do and what not to do.
Bad System Prompt (Avoid This)
You are a helpful assistant that reviews code and finds bugs and security issues
and performance problems and suggests improvements and rates them and also
provides a summary at the end.
This is one long sentence with no structure. Claude will follow it, but the output will be inconsistent. Bullet points and clear sections work much better.
Technique 1: Be Specific
The most common mistake is being too vague. Compare these prompts:
Vague: “Review this code”
Specific: “Review this Python function for potential null pointer exceptions, unhandled edge cases, and SQL injection vulnerabilities. For each issue, explain why it is dangerous and show the fixed code.”
The specific prompt gets a focused, useful response. The vague prompt gets a generic overview.
Python Example
import anthropic
client = anthropic.Anthropic()
code_to_review = """
def get_user(user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
result = db.execute(query)
return result[0]['name']
"""
# Vague prompt — generic response
vague = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": f"Review this code:\n{code_to_review}"}]
)
# Specific prompt — focused, actionable response
specific = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="You are a security-focused code reviewer. Only report security vulnerabilities and unhandled exceptions. For each issue, show the vulnerable line and the fixed code.",
messages=[{"role": "user", "content": f"Review this Python function:\n{code_to_review}"}]
)
The specific prompt finds the SQL injection and the potential IndexError. The vague prompt might mention them, or might talk about variable naming instead.
Technique 2: Use XML Tags
Claude responds especially well to XML tags in prompts. Use them to separate different parts of your prompt:
Python
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
system="""You are a technical writer. Convert code into documentation.
<rules>
- Write for developers who are new to this codebase
- Include a one-sentence summary, parameters table, return value, and example usage
- Use simple English
</rules>
<output_format>
## Function: {name}
{one sentence summary}
### Parameters
| Name | Type | Description |
...
### Returns
{description}
### Example
```python
{example code}
</output_format>""",
messages=[
{“role”: “user”, “content”: “”"
def calculate_tax(income: float, tax_rate: float, deductions: float = 0) -> float:
taxable = max(0, income - deductions)
return round(taxable * tax_rate, 2)
Document this function."""} ] )
print(message.content[0].text)
### TypeScript
```typescript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 2048,
system: `You are a technical writer. Convert code into documentation.
<rules>
- Write for developers who are new to this codebase
- Include a one-sentence summary, parameters table, return value, and example usage
- Use simple English
</rules>
<output_format>
## Function: {name}
{one sentence summary}
### Parameters
| Name | Type | Description |
...
### Returns
{description}
### Example
\`\`\`
{example code}
\`\`\`
</output_format>`,
messages: [
{
role: "user",
content: `<code>
function calculateTax(income: number, taxRate: number, deductions: number = 0): number {
const taxable = Math.max(0, income - deductions);
return Math.round(taxable * taxRate * 100) / 100;
}
</code>
Document this function.`,
},
],
});
if (message.content[0].type === "text") {
console.log(message.content[0].text);
}
Common XML tags in Claude prompts:
<instructions>— What to do<rules>— Constraints and requirements<context>— Background information<examples>— Example inputs and outputs<output_format>— Expected response format<code>— Code to analyze<document>— Document to process
XML tags help Claude distinguish between instructions and data. This is especially important when your prompt includes user-provided content that might look like instructions.
Technique 3: Few-Shot Prompting
Few-shot prompting means giving Claude examples of the input and output you want. Instead of describing what you want, you show it.
Python
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="""You extract structured data from product descriptions.
<examples>
<example>
<input>Apple MacBook Pro 14-inch with M4 Pro chip, 24GB RAM, 1TB SSD. $2,399.</input>
<output>{"product": "MacBook Pro 14-inch", "brand": "Apple", "specs": {"chip": "M4 Pro", "ram": "24GB", "storage": "1TB SSD"}, "price": 2399}</output>
</example>
<example>
<input>Samsung Galaxy S25 Ultra, 12GB RAM, 512GB storage, Snapdragon 8 Elite. Price: $1,299.99</input>
<output>{"product": "Galaxy S25 Ultra", "brand": "Samsung", "specs": {"chip": "Snapdragon 8 Elite", "ram": "12GB", "storage": "512GB"}, "price": 1299.99}</output>
</example>
</examples>
Always return valid JSON. No extra text.""",
messages=[
{"role": "user", "content": "Google Pixel 9 Pro, Tensor G4 chip, 16GB RAM, 256GB. Costs $999."}
]
)
print(message.content[0].text)
TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const message = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 1024,
system: `You extract structured data from product descriptions.
<examples>
<example>
<input>Apple MacBook Pro 14-inch with M4 Pro chip, 24GB RAM, 1TB SSD. $2,399.</input>
<output>{"product": "MacBook Pro 14-inch", "brand": "Apple", "specs": {"chip": "M4 Pro", "ram": "24GB", "storage": "1TB SSD"}, "price": 2399}</output>
</example>
<example>
<input>Samsung Galaxy S25 Ultra, 12GB RAM, 512GB storage, Snapdragon 8 Elite. Price: $1,299.99</input>
<output>{"product": "Galaxy S25 Ultra", "brand": "Samsung", "specs": {"chip": "Snapdragon 8 Elite", "ram": "12GB", "storage": "512GB"}, "price": 1299.99}</output>
</example>
</examples>
Always return valid JSON. No extra text.`,
messages: [
{ role: "user", content: "Google Pixel 9 Pro, Tensor G4 chip, 16GB RAM, 256GB. Costs $999." }
],
});
if (message.content[0].type === "text") {
console.log(message.content[0].text);
}
Expected output:
{"product": "Pixel 9 Pro", "brand": "Google", "specs": {"chip": "Tensor G4", "ram": "16GB", "storage": "256GB"}, "price": 999}
Few-shot prompting works because Claude learns the pattern from your examples. It matches the structure, field names, and formatting exactly.
When to Use Few-Shot
- Data extraction — Show the exact JSON structure you want
- Classification — Show examples of each category
- Formatting — Show the exact output format
- Tone matching — Show the writing style you want
One or two examples is usually enough. Only add more if the output is still not consistent.
Technique 4: Chain of Thought
Chain of thought means asking Claude to think through a problem step by step before giving the final answer. This improves accuracy for math, logic, and complex reasoning.
Python
import anthropic
client = anthropic.Anthropic()
# Without chain of thought
direct = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{
"role": "user",
"content": "A store sells apples for $2 each. Sam buys 3 apples and gives a $10 bill. He also has a coupon for 15% off. How much change does he get?"
}]
)
# With chain of thought
cot = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{
"role": "user",
"content": """A store sells apples for $2 each. Sam buys 3 apples and gives a $10 bill. He also has a coupon for 15% off. How much change does he get?
Think through this step by step. Show your reasoning, then give the final answer."""
}]
)
print("Direct:", direct.content[0].text)
print("\nChain of thought:", cot.content[0].text)
The chain of thought version breaks the problem down:
Step 1: Calculate the base cost: 3 apples x $2 = $6.00
Step 2: Apply the 15% discount: $6.00 x 0.15 = $0.90 discount
Step 3: Calculate the discounted total: $6.00 - $0.90 = $5.10
Step 4: Calculate the change: $10.00 - $5.10 = $4.90
Sam gets $4.90 in change.
When to Use Chain of Thought
- Math problems — Multi-step calculations
- Logic puzzles — Problems that require reasoning
- Debugging — Understanding why code fails
- Decision making — Weighing pros and cons
When NOT to Use Chain of Thought
- Simple questions — “What is the capital of France?”
- Classification — “Is this positive or negative?”
- Extraction — “Extract the email from this text”
For simple tasks, chain of thought adds unnecessary tokens and cost without improving accuracy.
Technique 5: Structured Instructions
Put your instructions first, data last. Claude pays the most attention to the beginning and end of the prompt. Instructions in the middle of long text can get lost.
Good Structure
import anthropic
client = anthropic.Anthropic()
article = """[Long article text here, 2000+ words...]"""
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="""You are a content editor. Summarize articles for a developer newsletter.
<rules>
- Maximum 3 bullet points
- Each bullet under 20 words
- Focus on practical takeaways, not background information
- Use present tense
</rules>""",
messages=[{
"role": "user",
"content": f"""<article>
{article}
</article>
Summarize this article following the rules in your system prompt."""
}]
)
Notice the structure:
- System prompt has instructions and rules (Claude reads this first)
- User message has the data wrapped in XML tags
- A short instruction at the end reminds Claude what to do
Bad Structure (Avoid This)
# Don't do this — instructions buried in the middle of data
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{
"role": "user",
"content": f"""{article}
Summarize this in 3 bullet points, each under 20 words, focusing on practical takeaways, using present tense."""
}]
)
This works for short prompts but fails when the article is very long. Claude may lose track of the instructions.
Temperature and Top-P
Claude has two parameters that control randomness:
- temperature (0.0 to 1.0) — Higher = more creative, lower = more deterministic
- top_p (0.0 to 1.0) — Controls the diversity of token selection
Python
import anthropic
client = anthropic.Anthropic()
# Deterministic (same output every time) — good for code and extraction
deterministic = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
temperature=0,
messages=[{"role": "user", "content": "Write a Python function to validate email addresses"}]
)
# Creative — good for brainstorming and writing
creative = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
temperature=0.8,
messages=[{"role": "user", "content": "Give me 5 creative project ideas using Claude's API"}]
)
When to Adjust Temperature
| Use Case | Temperature |
|---|---|
| Code generation | 0 |
| Data extraction | 0 |
| Classification | 0 |
| General coding | 0-0.3 |
| Writing | 0.5-0.7 |
| Brainstorming | 0.7-1.0 |
For most developer tasks, the default temperature (1.0) works fine. Only lower it when you need deterministic, reproducible output.
Common Mistakes
1. Conflicting Instructions
# Bad — contradictory rules
system = "Be concise. Also, explain everything in detail."
Pick one. If you need both, specify when to be concise and when to be detailed.
2. Too Many Instructions
A system prompt with 50 rules is hard for any model to follow perfectly. Keep it under 10-15 key rules. Use the most important rules first.
3. No Examples
Describing what you want takes many words. Showing what you want with an example is faster and more reliable.
4. Forgetting the Output Format
If you do not specify an output format, Claude picks one. Sometimes it uses markdown, sometimes plain text, sometimes JSON. Always specify the format if it matters.
5. Not Giving Permission to Say “I Don’t Know”
system = """You are a technical assistant.
If you are unsure about something, say "I'm not sure about this" rather than guessing.
Do not make up information about APIs, libraries, or version numbers."""
This reduces hallucinations. Claude tends to be confident even when wrong. Give it explicit permission to express uncertainty.
Real-World Example: Code Review Prompt
Here is a complete, production-ready code review prompt that combines all five techniques:
Python
import anthropic
client = anthropic.Anthropic()
system_prompt = """You are a senior code reviewer specializing in Python.
<rules>
- Focus on: bugs, security vulnerabilities, performance issues, and error handling
- Ignore: style preferences, formatting, variable naming
- Rate each issue as: CRITICAL (must fix), WARNING (should fix), or INFO (nice to have)
- If the code is good, say "No issues found" — do not invent problems
- If you are unsure about an issue, say so
</rules>
<output_format>
For each issue:
- **[SEVERITY] Line X: Brief title**
- Problem: What is wrong
- Fix: Show the corrected code
Summary:
- Critical: N issues
- Warning: N issues
- Info: N issues
</output_format>
<examples>
<example>
<input>
def divide(a, b):
return a / b
</input>
<output>
- **[CRITICAL] Line 2: Division by zero not handled**
- Problem: If `b` is 0, this raises a `ZeroDivisionError`
- Fix: `if b == 0: raise ValueError("Cannot divide by zero")`
Summary:
- Critical: 1 issue
- Warning: 0 issues
- Info: 0 issues
</output>
</example>
</examples>"""
code = """
def process_payment(user_id, amount, currency="USD"):
user = db.query(f"SELECT * FROM users WHERE id = {user_id}")
if user:
balance = user[0]["balance"]
if balance >= amount:
new_balance = balance - amount
db.execute(f"UPDATE users SET balance = {new_balance} WHERE id = {user_id}")
return {"status": "success", "new_balance": new_balance}
return None
"""
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
system=system_prompt,
messages=[{"role": "user", "content": f"<code>\n{code}\n</code>\n\nReview this function."}]
)
print(message.content[0].text)
This prompt uses:
- Specific instructions — Focus on bugs, ignore style
- XML tags — Separate rules, format, examples, and code
- Few-shot — One example of the expected output
- Structured output — Severity ratings and summary
- Permission to say no issues — Prevents invented problems
Summary
| Technique | When to Use | Benefit |
|---|---|---|
| Be specific | Always | Focused, actionable responses |
| XML tags | Long or complex prompts | Clear separation of instructions and data |
| Few-shot | Data extraction, classification, formatting | Consistent output structure |
| Chain of thought | Math, logic, complex reasoning | Higher accuracy |
| Structured instructions | Long documents, multi-step tasks | Claude follows all instructions |
Start simple. Add techniques only when the basic prompt does not give you what you need.
What’s Next?
In the next article, we will cover CLAUDE.md — project instruction files that make Claude Code smarter about your specific codebase.
Next: CLAUDE.md — Project Instructions That Make Claude Smarter