Prompt Engineering Fundamentals Every Developer Should Know
Table of Contents
You’re integrating an LLM into your application, and the outputs are inconsistent. Sometimes brilliant, sometimes garbage. The difference isn’t the model — it’s how you’re prompting it. Prompt engineering fundamentals separate developers who get reliable AI outputs from those fighting unpredictable responses.
This isn’t about finding magic phrases. It’s about understanding how LLMs process instructions and structuring your prompts accordingly.
System Prompts: Setting the Operating Context
The system prompt defines who the AI is and how it should behave. Think of it as the configuration layer that runs before every user interaction.
import anthropic
client = anthropic.Anthropic()
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system="""You are a senior code reviewer for a Python development team.
RULES:
- Focus on security vulnerabilities and performance issues
- Ignore style preferences unless they impact readability
- Rate severity as: critical, warning, or suggestion
- Always explain WHY something is problematic""",
messages=[
{"role": "user", "content": "Review this function: def get_user(id): return db.execute(f'SELECT * FROM users WHERE id = {id}')"}
]
)
The system prompt above does several things right:
- Establishes a specific role (code reviewer, not generic assistant)
- Sets clear constraints on what to focus on
- Defines output format expectations
- Explains the reasoning requirement
If you’ve already built your first AI agent with Python and Claude, you’ve seen how system prompts shape agent behavior. For prompt engineering, this is your foundation.
Few-Shot Examples: Teaching by Demonstration
When you need consistent output formatting or want the model to follow a specific reasoning pattern, show it examples. Few-shot prompting works because LLMs are pattern-matching machines.
Convert user feedback into structured bug reports.
Example 1:
Input: "The app crashes when I click the submit button on the contact form"
Output:
{
"component": "contact-form",
"action": "submit",
"issue_type": "crash",
"reproduction": "Click submit button on contact form"
}
Example 2:
Input: "Loading takes forever on the dashboard after login"
Output:
{
"component": "dashboard",
"action": "page-load",
"issue_type": "performance",
"reproduction": "Login and navigate to dashboard"
}
Now convert this:
Input: "Profile picture upload fails with large images"
Three rules for effective few-shot examples:
- Use 2-4 examples — more isn’t always better
- Cover edge cases or variations you care about
- Keep examples structurally identical to what you expect
Chain-of-Thought: Making the Model Show Its Work
For complex reasoning tasks, explicitly asking the model to think step-by-step dramatically improves accuracy. This is chain-of-thought prompting.
Analyze this database query for potential issues. Think through each part step by step before giving your final assessment.
Query:
SELECT u.*, o.total
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.created_at > '2024-01-01'
The model will now walk through the query systematically rather than jumping to conclusions. You can make this even more explicit:
Before answering:
1. Identify what the query is trying to accomplish
2. Check for logical errors in the JOIN conditions
3. Consider performance implications
4. Look for data integrity issues
Then provide your assessment.
Chain-of-thought is especially useful when you’re building RAG pipelines where the model needs to reason over retrieved context.
XML Tags: Structured Input for Structured Output
XML tags help you clearly delineate different parts of your prompt. Claude in particular handles these well, but the technique works across models.
<context>
You are helping a developer debug a Laravel application. The application uses MySQL and Redis for caching.
</context>
<error_log>
[2026-05-07 09:15:32] production.ERROR: SQLSTATE[HY000] [2002] Connection refused
</error_log>
<recent_changes>
- Updated .env file with new database credentials
- Deployed new feature branch to production
- Cleared config cache
</recent_changes>
<task>
Identify the most likely cause of this error and provide specific debugging steps.
</task>
This structure does several things:
- Separates context from the actual task
- Makes it easy to swap out sections programmatically
- Reduces ambiguity about what’s data vs. instruction
If you’re setting up Cursor rules for AI-assisted coding, you’ll use similar structural patterns to give the AI consistent context.
Output Format Control: Getting Parseable Responses
When you need to parse AI output programmatically, be explicit about format requirements.
Analyze the following API endpoint for RESTful design issues.
Endpoint: POST /api/getUserData
Respond in this exact JSON format:
{
"issues": [
{
"type": "naming|method|structure",
"description": "string",
"suggestion": "string"
}
],
"overall_score": 1-10
}
Return ONLY valid JSON. No markdown code blocks. No explanations outside the JSON structure.
The key phrases here are:
- “exact JSON format” — signals strict adherence
- “Return ONLY valid JSON” — prevents preamble text
- “No markdown code blocks” — stops the model from wrapping output in ```json
For production code, always validate the output. Models occasionally deviate even with strict instructions.
Putting It All Together
Here’s a complete example combining these techniques for a code documentation generator:
system_prompt = """You are a technical documentation writer specializing in PHP/Laravel codebases.
STYLE GUIDE:
- Write for intermediate developers
- Include parameter types and return types
- Add usage examples for non-obvious functions
- Keep descriptions under 3 sentences"""
user_prompt = """<function>
public function syncUserPermissions(User $user, array $permissions, bool $detach = true): void
{
$permissionIds = Permission::whereIn('slug', $permissions)->pluck('id');
$user->permissions()->sync($permissionIds, $detach);
Cache::tags(['permissions', "user:{$user->id}"])->flush();
}
</function>
<output_format>
Return documentation in this structure:
{
"summary": "One-line description",
"parameters": [{"name": "", "type": "", "description": ""}],
"returns": {"type": "", "description": ""},
"example": "Code example string",
"notes": ["Array of important considerations"]
}
</output_format>
Generate documentation for this function."""
Key Takeaways
- System prompts set behavior rules that apply to every interaction — use them to establish role, constraints, and output expectations
- Few-shot examples teach patterns more reliably than descriptions — show, don’t just tell
- Chain-of-thought prompting improves accuracy on complex tasks — ask the model to reason step-by-step
- XML tags provide clear structure that survives complex prompts — especially useful when mixing data with instructions
- Output format control requires explicit instructions and validation — never assume the model will follow format perfectly
These fundamentals apply whether you’re running Llama locally or hitting cloud APIs. Master them, and your AI integrations become predictable tools rather than unpredictable experiments.