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:

  1. Role — Who Claude should be (one line)
  2. Rules — What Claude should always or never do (bullet points)
  3. 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:

  1. System prompt has instructions and rules (Claude reads this first)
  2. User message has the data wrapped in XML tags
  3. 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 CaseTemperature
Code generation0
Data extraction0
Classification0
General coding0-0.3
Writing0.5-0.7
Brainstorming0.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:

  1. Specific instructions — Focus on bugs, ignore style
  2. XML tags — Separate rules, format, examples, and code
  3. Few-shot — One example of the expected output
  4. Structured output — Severity ratings and summary
  5. Permission to say no issues — Prevents invented problems

Summary

TechniqueWhen to UseBenefit
Be specificAlwaysFocused, actionable responses
XML tagsLong or complex promptsClear separation of instructions and data
Few-shotData extraction, classification, formattingConsistent output structure
Chain of thoughtMath, logic, complex reasoningHigher accuracy
Structured instructionsLong documents, multi-step tasksClaude 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