Claude is one of the best AI models for code generation. Sonnet 4.6 is the first Sonnet model to beat the previous Opus in coding benchmarks. But good code generation depends on how you ask. This article covers 10 best practices that will get you better code from Claude every time.
This is Article 19 in the Claude AI — From Zero to Power User series. You should know Prompt Engineering Basics and Tool Use before this article.
Why Claude for Code Generation?
Claude excels at:
- Reasoning through complex logic — Multi-step algorithms, data transformations
- Understanding large codebases — 200K token context (1M in beta)
- Multi-file generation — Creating related files that work together
- Debugging — Analyzing error messages and stack traces
- Refactoring — Restructuring code without changing behavior
Sonnet 4.6 hits the sweet spot for most developers: it is fast, relatively cheap, and produces high-quality code. Use Opus 4.6 for the most complex tasks.
Best Practice 1: Provide Full Context
Claude generates better code when it knows your project structure, dependencies, and existing patterns.
Python
import anthropic
client = anthropic.Anthropic()
system_prompt = """You are a Python developer working on a FastAPI project.
<project_context>
- Framework: FastAPI 0.115+
- Database: PostgreSQL with SQLAlchemy 2.0+ (async)
- Auth: JWT tokens via python-jose
- Validation: Pydantic v2
- Testing: pytest with pytest-asyncio
- Style: Black formatting, type hints required, Google-style docstrings
</project_context>
<existing_patterns>
- All routes return Pydantic response models
- All database operations use async sessions
- Errors use HTTPException with detail messages
- Auth dependency: `current_user = Depends(get_current_user)`
</existing_patterns>"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
system=system_prompt,
messages=[
{
"role": "user",
"content": "Write a CRUD endpoint for a 'projects' resource with name, description, and owner_id fields.",
}
],
)
print(response.content[0].text)
TypeScript
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
const systemPrompt = `You are a TypeScript developer working on a Next.js 15 project.
<project_context>
- Framework: Next.js 15 with App Router
- Database: Prisma with PostgreSQL
- Auth: NextAuth.js v5
- Validation: Zod
- Styling: Tailwind CSS
- Testing: Vitest + React Testing Library
</project_context>
<existing_patterns>
- Server actions for mutations
- Server components by default, client components only when needed
- Zod schemas for all form validation
- Error boundaries for error handling
</existing_patterns>`;
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 4096,
system: systemPrompt,
messages: [
{
role: "user",
content:
"Write a server action for creating a new project with name, description, and team members.",
},
],
});
Without context, Claude guesses your framework, style, and patterns. With context, the generated code fits into your project immediately.
Best Practice 2: Be Specific About Requirements
Vague requests get generic code. Specific requests get production-ready code.
Vague: “Write a function to process payments”
Specific:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
system="You are a backend developer. Write production-ready Python code.",
messages=[{
"role": "user",
"content": """Write a payment processing function with these requirements:
<requirements>
- Accept amount (Decimal), currency (str), and payment_method_id (str)
- Validate amount > 0 and currency is ISO 4217
- Call Stripe API to create a PaymentIntent
- Handle StripeError, network errors, and invalid input
- Return a PaymentResult dataclass with status, transaction_id, and error_message
- Log all operations with structlog
- Include type hints and docstring
</requirements>"""
}],
)
The more specific you are, the fewer rounds of revision you need.
Best Practice 3: Include Existing Code
When you want Claude to extend existing code, include the relevant files:
existing_model = """
# models/user.py
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, nullable=False)
name = Column(String, nullable=False)
"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"""<existing_code>
{existing_model}
</existing_code>
Add a Project model that:
- Has fields: name, description, created_at
- Has a foreign key to User (owner)
- Has a many-to-many relationship with User (members)
- Follows the same patterns as the User model above"""
}],
)
Claude will match the style, imports, and patterns of your existing code.
Best Practice 4: Language-Specific Instructions
Different languages have different conventions. Tell Claude which conventions to follow:
Python
system = """You write Python code following these conventions:
- Python 3.12+ features (type hints, match statements, f-strings)
- PEP 8 style, formatted with Black
- All functions have type hints for parameters and return values
- Use dataclasses or Pydantic models for data structures
- Prefer pathlib over os.path
- Use contextlib for resource management
- Write Google-style docstrings"""
TypeScript
const system = `You write TypeScript code following these conventions:
- TypeScript 5.5+ strict mode
- Prefer interfaces over types for object shapes
- Use readonly where possible
- Prefer const assertions
- Use Zod for runtime validation
- Prefer async/await over raw Promises
- Use ESM imports, not CommonJS require`;
Go
system = """You write Go code following these conventions:
- Go 1.23+ features
- Standard library first, external packages only when necessary
- Error handling: return errors, wrap with fmt.Errorf and %w
- Use context.Context for cancellation and timeouts
- Table-driven tests
- Interfaces accepted, structs returned"""
Best Practice 5: Ask for Tests
Always ask Claude to write tests alongside the code:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=8192,
messages=[{
"role": "user",
"content": """Write a URL shortener service in Python with:
1. A function to shorten a URL (generate a 6-character code)
2. A function to resolve a short code back to the original URL
3. Storage in a dict (in-memory for now)
4. Validation that the URL is valid
Then write comprehensive tests using pytest:
- Happy path tests
- Edge cases (empty URL, invalid URL, non-existent code)
- Test that shortened URLs are unique"""
}],
)
Claude generates better code when it knows tests will verify it.
Best Practice 6: Use Extended Thinking for Complex Logic
For complex algorithms, enable extended thinking so Claude reasons through the problem before writing code:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=16384,
thinking={
"type": "enabled",
"budget_tokens": 10000,
},
messages=[{
"role": "user",
"content": """Implement a rate limiter with these requirements:
- Token bucket algorithm
- Configurable rate (tokens per second) and burst size
- Thread-safe for concurrent access
- Support for per-user rate limiting
- Sliding window for accurate rate tracking
- Return remaining tokens and reset time in the response"""
}],
)
# The thinking block shows Claude's reasoning
for block in response.content:
if block.type == "thinking":
print(f"Thinking: {block.thinking[:200]}...")
elif block.type == "text":
print(f"Code:\n{block.text}")
Extended thinking costs extra tokens but significantly improves accuracy for complex logic, algorithms, and multi-step problems.
Best Practice 7: Multi-File Generation
Claude can generate multiple related files in one request. Use a clear structure:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=8192,
system="Generate code with clear file path headers. Use ```language:path/to/file.ext format.",
messages=[{
"role": "user",
"content": """Create a complete REST API for a todo app:
Files needed:
1. models/todo.py — SQLAlchemy model
2. schemas/todo.py — Pydantic request/response schemas
3. routes/todo.py — FastAPI route handlers (CRUD)
4. tests/test_todo.py — pytest tests for all endpoints
Requirements:
- Fields: id, title, description, completed, created_at, updated_at
- Endpoints: GET /todos, GET /todos/{id}, POST /todos, PUT /todos/{id}, DELETE /todos/{id}
- Filtering: GET /todos?completed=true
- Pagination: GET /todos?page=1&size=10"""
}],
)
Best Practice 8: Refactoring Prompts
When refactoring, tell Claude what to change and what to keep:
code_to_refactor = """
def process_order(order_data):
# validate
if not order_data.get('items'):
return {'error': 'No items'}
if not order_data.get('customer_id'):
return {'error': 'No customer'}
total = 0
for item in order_data['items']:
if item['quantity'] < 1:
return {'error': 'Invalid quantity'}
total += item['price'] * item['quantity']
# apply discount
if order_data.get('coupon'):
if order_data['coupon'] == 'SAVE10':
total *= 0.9
elif order_data['coupon'] == 'SAVE20':
total *= 0.8
# save
order = db.orders.insert({'customer_id': order_data['customer_id'], 'items': order_data['items'], 'total': total})
return {'order_id': order.id, 'total': total}
"""
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
messages=[{
"role": "user",
"content": f"""Refactor this function:
```python
{code_to_refactor}
The key phrase is “keep the same business logic.” This tells Claude to restructure without changing behavior.
Best Practice 9: Debugging with Error Context
When debugging, give Claude the error, the code, and what you expected:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
messages=[{
"role": "user",
"content": """I am getting this error:
<error>
TypeError: Cannot read properties of undefined (reading 'map')
at UserList (components/UserList.tsx:15:28)
at renderWithHooks (react-dom.development.js:14985:18)
</error>
<code>
// components/UserList.tsx
import { useEffect, useState } from 'react';
export function UserList() {
const [users, setUsers] = useState();
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => setUsers(data));
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
</code>
<expected>
The component should render a list of users fetched from the API.
</expected>
Find the bug and show the fixed code."""
}],
)
The more context you give, the faster Claude finds the root cause.
Best Practice 10: Use Tool Use for Verification
Let Claude run linters, type checkers, or tests to verify its own code:
tools = [
{
"name": "run_typecheck",
"description": "Run TypeScript type checker on the given code",
"input_schema": {
"type": "object",
"properties": {
"code": {"type": "string", "description": "TypeScript code to check"},
},
"required": ["code"],
},
},
{
"name": "run_linter",
"description": "Run ESLint on the given code",
"input_schema": {
"type": "object",
"properties": {
"code": {"type": "string", "description": "Code to lint"},
},
"required": ["code"],
},
},
]
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
tools=tools,
messages=[{
"role": "user",
"content": "Write a TypeScript function to parse CSV files. After writing, run the type checker and linter to verify."
}],
)
This creates a feedback loop where Claude can fix its own mistakes before you see the output.
Checklist: 10 Rules for Better Code Generation
Use this checklist for every code generation request:
- Provide project context (framework, dependencies, style)
- Be specific about requirements (inputs, outputs, edge cases)
- Include existing code for style matching
- Specify language conventions explicitly
- Ask for tests alongside the code
- Enable extended thinking for complex algorithms
- Request multiple files when appropriate
- For refactoring, say “keep the same behavior”
- For debugging, include error, code, and expected behavior
- Use tool use for automated verification
Model Selection for Code Generation
| Task | Recommended Model | Why |
|---|---|---|
| Simple functions | Haiku 4.5 | Fast, cheap, good enough |
| Standard CRUD, APIs | Sonnet 4.6 | Best price/quality ratio |
| Complex algorithms | Opus 4.6 | Better reasoning |
| Refactoring large files | Opus 4.6 | Needs deep understanding |
| Test generation | Sonnet 4.6 | Good coverage, fast |
| Quick prototypes | Sonnet 4.6 | Fast iteration |
Cost per Task
| Task | Model | Typical Cost |
|---|---|---|
| Single function | Sonnet 4.6 | $0.01-0.03 |
| CRUD endpoint | Sonnet 4.6 | $0.03-0.08 |
| Multi-file generation | Sonnet 4.6 | $0.05-0.15 |
| Complex algorithm + thinking | Opus 4.6 | $0.20-0.50 |
| Debugging session (3 rounds) | Sonnet 4.6 | $0.05-0.10 |
Summary
| Best Practice | Key Benefit |
|---|---|
| Provide context | Code fits your project |
| Be specific | Fewer revision rounds |
| Include existing code | Style matching |
| Language conventions | Idiomatic code |
| Ask for tests | Verified correctness |
| Extended thinking | Better complex logic |
| Multi-file | Complete features |
| Refactoring rules | Safe restructuring |
| Debug with context | Faster root cause |
| Tool verification | Self-correcting |
What’s Next?
In the next article, we will build a real project: a CLI chatbot with conversation history, streaming, and tool use.
Next: Build a CLI Chatbot with Claude API