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}
- Split into smaller functions (validate, calculate_total, apply_discount, save) - Use Pydantic models for input validation - Use proper error handling (raise exceptions, not return dicts) - Add type hints and docstrings - Keep the same business logic — do not change behavior """ }], ) ```

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:

  1. Provide project context (framework, dependencies, style)
  2. Be specific about requirements (inputs, outputs, edge cases)
  3. Include existing code for style matching
  4. Specify language conventions explicitly
  5. Ask for tests alongside the code
  6. Enable extended thinking for complex algorithms
  7. Request multiple files when appropriate
  8. For refactoring, say “keep the same behavior”
  9. For debugging, include error, code, and expected behavior
  10. Use tool use for automated verification

Model Selection for Code Generation

TaskRecommended ModelWhy
Simple functionsHaiku 4.5Fast, cheap, good enough
Standard CRUD, APIsSonnet 4.6Best price/quality ratio
Complex algorithmsOpus 4.6Better reasoning
Refactoring large filesOpus 4.6Needs deep understanding
Test generationSonnet 4.6Good coverage, fast
Quick prototypesSonnet 4.6Fast iteration

Cost per Task

TaskModelTypical Cost
Single functionSonnet 4.6$0.01-0.03
CRUD endpointSonnet 4.6$0.03-0.08
Multi-file generationSonnet 4.6$0.05-0.15
Complex algorithm + thinkingOpus 4.6$0.20-0.50
Debugging session (3 rounds)Sonnet 4.6$0.05-0.10

Summary

Best PracticeKey Benefit
Provide contextCode fits your project
Be specificFewer revision rounds
Include existing codeStyle matching
Language conventionsIdiomatic code
Ask for testsVerified correctness
Extended thinkingBetter complex logic
Multi-fileComplete features
Refactoring rulesSafe restructuring
Debug with contextFaster root cause
Tool verificationSelf-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