MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

Prompt Engineering for Code Generation

Optimize prompts for AI code generation: context, examples, and constraints.

AIPrompt EngineeringCode GenerationGPT

By MinhVo

Introduction

Prompt engineering for code generation has become one of the most impactful skills in modern software development. Large language models like GPT-4, Claude, and Gemini can generate functional, production-quality code when given well-structured prompts, but the difference between a mediocre prompt and an expertly crafted one can be the difference between code that works and code that fails silently. The quality of the code you receive from an AI model is directly proportional to the specificity, structure, and context you provide in your prompt.

Unlike natural language prompts where ambiguity is tolerable, code generation demands precision. A model needs to understand the programming language, framework version, project conventions, error handling requirements, type constraints, and the surrounding codebase context to produce code that integrates seamlessly. A vague prompt like "write a function to fetch users" produces generic code that likely does not match your project's patterns, while a precise prompt that specifies the ORM, response types, error handling strategy, and naming conventions produces code that a senior developer would be proud to commit.

This guide provides a systematic framework for engineering prompts that produce high-quality, production-ready code. We will cover prompt architecture, context injection, few-shot examples, chain-of-thought techniques, constraint specification, and advanced patterns for generating complex multi-file implementations.

AI code generation workflow

Understanding Code Generation Prompts: Core Concepts

The Anatomy of an Effective Code Prompt

Every high-quality code generation prompt consists of four components: a role that sets the model's persona and expertise level, a task that precisely describes what code to generate, context that provides the surrounding codebase information the model needs, and constraints that define quality requirements, patterns, and limitations.

The role component is often underestimated. Specifying "You are a senior TypeScript developer following strict functional programming patterns with Zod for runtime validation" produces fundamentally different code than "Write a function." The role primes the model's code style, error handling approach, and architectural decisions before it generates a single line.

Context Window Management

LLMs have finite context windows. For code generation, this means you must carefully curate what code you include. Include the types and interfaces the generated code must use, the function signatures it must call or implement, the project's coding conventions, and any relevant error handling patterns. Do not include unrelated code that wastes context window space.

Temperature and Creativity

For code generation, lower temperatures (0.0-0.3) produce more deterministic, syntactically correct code. Higher temperatures (0.5-0.8) encourage creative solutions but increase the risk of hallucinated APIs or logical errors. Use low temperature for production code generation and moderate temperature for exploring alternative approaches or architecture brainstorming.

Prompt engineering framework

Architecture and Design Patterns

The Context-Task-Constraints (CTC) Framework

The most reliable prompt structure for code generation follows the CTC pattern. First, provide all necessary context — types, interfaces, existing functions, and project conventions. Second, describe the task with precise requirements including inputs, outputs, and behavior. Third, specify constraints — what the code must NOT do, required error handling, performance requirements, and style guidelines.

## Context
We use TypeScript with strict mode, Zod for validation, and Prisma for database access.
Our error handling uses a custom AppError class with typed error codes.
All API routes follow this pattern: [existing code example]
 
## Task
Create a POST /api/orders endpoint that validates the request body,
creates an order in the database, and returns the created order.
 
## Constraints
- Use Zod schemas for request validation
- Wrap database operations in Prisma transactions
- Return typed responses matching our ApiResponse<T> interface
- Do NOT use any — all types must be explicit
- Handle the case where the product is out of stock

Few-Shot Code Generation

Providing examples of the desired code style and patterns is the most effective way to ensure consistency. Include 1-3 examples of existing code that follow the same patterns you want the generated code to follow.

## Example: Existing user creation endpoint
```typescript
export async function POST(request: Request) {
  const body = await request.json();
  const parsed = CreateUserSchema.safeParse(body);
 
  if (!parsed.success) {
    return NextResponse.json(
      { success: false, error: { code: 'VALIDATION_ERROR', details: parsed.error.issues } },
      { status: 400 }
    );
  }
 
  const user = await prisma.user.create({ data: parsed.data });
  return NextResponse.json({ success: true, data: user }, { status: 201 });
}

Now create the order creation endpoint following the exact same pattern.


### Chain-of-Thought for Complex Code

For complex implementations, ask the model to reason through the approach before writing code. This produces better results because the model considers edge cases, error states, and architectural implications before committing to an implementation.

```markdown
Before writing code, think through:
1. What are all the input validation cases I need to handle?
2. What database operations are needed and should they be in a transaction?
3. What error states can occur and how should each be handled?
4. What are the performance implications of this approach?

Then provide your implementation plan, followed by the complete code.

Step-by-Step Implementation

Generating CRUD Operations

The most common code generation task is CRUD operations. Structure your prompt to include the database schema, the expected request/response types, and the validation requirements.

Given this Prisma schema:
```prisma
model Product {
  id          String   @id @default(cuid())
  name        String
  description String?
  price       Float
  stock       Int      @default(0)
  categoryId  String
  category    Category @relation(fields: [categoryId], references: [id])
  createdAt   DateTime @default(now())
}

Generate a complete CRUD service for Product with these requirements:

  • TypeScript with strict typing
  • Zod validation for all inputs
  • Cursor-based pagination for list operations
  • Filtering by category, price range, and name search
  • Proper error handling with custom error types
  • Include JSDoc comments for all public methods

### Generating API Route Handlers

```markdown
Generate an Express.js route handler for file uploads with:
- Multipart form data using multer
- File type validation (only images: jpg, png, webp)
- File size limit of 5MB
- Automatic image optimization using sharp (resize to max 1200px width)
- S3 upload using AWS SDK v3
- Return the public URL of the uploaded file
- Proper error handling for each failure point

Context: We use Express with TypeScript, express-validator for validation,
and a custom middleware for authentication.

Generating Test Suites

Generate a comprehensive test suite for this function:
 
```typescript
export async function createOrder(
  userId: string,
  items: Array<{ productId: string; quantity: number }>
): Promise<Order> {
  // implementation omitted
}

Test requirements:

  • Use Vitest with TypeScript
  • Mock Prisma client using vitest-mock-extended
  • Test happy path: valid order with sufficient stock
  • Test error cases: insufficient stock, invalid product ID, empty items
  • Test edge cases: exactly zero stock, maximum quantity
  • Test transaction rollback on partial failure
  • Include setup and teardown with database cleanup

### Generating Configuration and Infrastructure Code

```markdown
Generate a GitHub Actions workflow for a Next.js application with:
- Trigger on push to main and pull requests
- Node.js 20 with corepack for pnpm
- Steps: install dependencies, type check, lint, test, build
- Cache node_modules using pnpm store
- Deploy to Vercel on push to main (not PRs)
- Environment secrets from GitHub secrets
- Add a status check that must pass before merge

Code generation pipeline

Real-World Use Cases

Use Case 1: Rapid API Development

A startup needs to ship a REST API with 20 endpoints in two days. By defining the database schema and providing a template for one complete endpoint (controller, service, repository, tests), the developer generates the remaining 19 endpoints with consistent patterns. The prompt includes the Prisma schema, the example endpoint, and constraints around error handling and validation.

Use Case 2: Legacy Code Migration

A team migrating from JavaScript to TypeScript uses prompts to generate typed versions of existing functions. The prompt includes the original JavaScript code, the target TypeScript conventions (strict mode, no any, explicit return types), and the surrounding type definitions. The model produces TypeScript code with proper generics, union types, and discriminated unions.

Use Case 3: Database Migration Scripts

When restructuring a database schema, developers use prompts to generate migration scripts that transform data from the old format to the new format. The prompt describes the before and after schemas, provides the transformation rules, and specifies rollback requirements. The model generates idempotent SQL migrations with proper error handling.

Use Case 4: Boilerplate Reduction

Teams use prompts to generate repetitive patterns — form components, API clients, GraphQL resolvers, and configuration files. By providing the schema and one example, the model generates dozens of consistent implementations that follow the exact same structure, eliminating the tedium and reducing the risk of copy-paste errors.

Best Practices for Production

  1. Include type definitions in every prompt: Always provide the TypeScript interfaces, Zod schemas, or Prisma models that the generated code must use. Without type context, the model invents its own type structure that will not integrate with your codebase.

  2. Provide one complete example, then ask for variations: The few-shot approach works best when you show one fully working implementation and ask the model to follow the same pattern for a different entity or endpoint. The model internalizes the pattern and applies it consistently.

  3. Specify what NOT to do: Explicit constraints like "do not use any", "do not use deprecated APIs", "do not add comments that restate the code" are often more effective than positive instructions because they prevent the model's default behaviors.

  4. Use incremental generation for complex features: Instead of generating an entire feature in one prompt, break it into layers — types first, then the data access layer, then the service layer, then the API routes, then tests. Each step can reference the output of the previous step.

  5. Validate generated code before integration: Always run the linter, type checker, and tests on generated code. Models can produce code that compiles but has subtle logical errors, off-by-one mistakes, or missing edge case handling that only automated checks catch.

  6. Iterate on prompts based on output quality: Prompt engineering is empirical. When generated code misses the mark, analyze what was ambiguous in your prompt and add specificity. Keep a library of proven prompts for recurring tasks.

  7. Include error handling requirements explicitly: Models often generate code that only handles the happy path. Specify error handling requirements in every prompt — what exceptions to catch, how to log errors, what to return to the client, and when to retry.

  8. Provide the surrounding file structure: Include the project's directory structure so the model understands where files should be placed and how they relate to each other. This prevents the model from generating imports that reference non-existent paths.

Common Pitfalls and Solutions

PitfallImpactSolution
Vague prompts producing generic codeGenerated code does not match project conventionsProvide specific examples, type definitions, and style requirements
Missing type contextModel invents types that conflict with existing codebaseInclude all relevant interfaces, schemas, and type definitions
No error handling in generated codeHappy-path-only code that fails in productionExplicitly request error handling for each failure mode
Hallucinated APIs or methodsCode references functions that do not exist in the library versionSpecify exact library versions and provide API documentation snippets
Inconsistent patterns across filesEach generated file follows a different structureProvide one canonical example and ask for the same pattern
Over-reliance without code reviewSubtle bugs pass through without human reviewAlways run type checks, linters, and tests; treat generated code as a draft

Performance Optimization

Optimizing Prompt Length

Longer prompts consume more context window and cost more per request. Optimize by including only the types and code that directly influence the output. Remove comments and whitespace from included code snippets. Use abbreviations for well-known patterns ("CRUD" instead of explaining each operation).

When generating related files (e.g., all CRUD endpoints for a set of entities), batch them in a single prompt with shared context. The model maintains consistency better when it generates related code in one session rather than across multiple independent sessions.

Caching and Reusing Effective Prompts

Build a library of proven prompts organized by task type — API endpoints, database services, test suites, component templates, infrastructure configurations. Version these prompts alongside the code they generate so you can regenerate code when conventions change.

Comparison with Approaches

ApproachSpeedConsistencyContext AwarenessCustomization
Manual codingSlowDepends on developerFullUnlimited
AI code generation (poor prompts)FastLow — inconsistent outputMinimalLimited
AI code generation (expert prompts)FastHigh — matches conventionsGood with contextHigh
Template-based scaffoldingVery fastPerfectNoneTemplate-locked
IDE code completion (Copilot)InlineModerateGood (reads open files)Limited to suggestions
Code generation agents (Cursor, Cody)FastModerateProject-wideModerate

Advanced Patterns

Multi-File Generation with Dependency Ordering

Generate a complete feature module for "product reviews" with the following files:
1. `types/review.ts` — TypeScript interfaces and Zod schemas
2. `repositories/review.repository.ts` — Prisma data access layer
3. `services/review.service.ts` — Business logic with validation
4. `routes/review.routes.ts` — Express router with middleware
5. `tests/review.test.ts` — Comprehensive test suite
 
The module must support:
- Creating reviews with ratings 1-5 and optional text
- Calculating average rating per product
- Pagination and sorting by date or rating
- Only users who purchased the product can review it
 
Start with the types file, then use those types in all subsequent files.
Include all imports and ensure every file compiles with strict TypeScript.

Iterative Refinement Prompts

I've reviewed the code you generated. Please make the following changes:
1. Replace the try-catch in the service layer with our Result<T, E> pattern:
   [include Result type definition]
2. Add request deduplication using our rate limiting middleware
3. The Zod schema should use .refine() to validate that endDate > startDate
4. Add comprehensive JSDoc with @example tags for each public function
 
Keep all other patterns consistent with the original generation.

Testing Strategies

Validating Generated Code Quality

After generating the code, verify it against these criteria:
1. Run `tsc --noEmit` — no type errors
2. Run `eslint . --max-warnings 0` — no lint warnings
3. Run `vitest run` — all tests pass
4. Check that no `any` types exist in the codebase
5. Verify all error paths return appropriate error responses
6. Confirm database operations use transactions where required

Generating Edge Case Tests

For the function I just showed you, generate additional test cases covering:
- Boundary values (empty array, single item, maximum array length)
- Concurrent access scenarios
- Database connection failures
- Timeout scenarios
- Malformed input at every validation point
- Race conditions in the inventory check + order creation flow

Future Outlook

The future of code generation is moving toward agentic workflows where AI systems can not only generate code but also run it, observe failures, and iteratively fix issues. Tools like Cursor, Cline, and Claude's computer-use capabilities are enabling multi-step generation where the AI writes code, runs the test suite, reads error messages, and refines its output without human intervention.

Context-aware generation is improving rapidly. Modern code generation tools can index entire repositories, understand project conventions through codebase analysis, and generate code that perfectly matches existing patterns. The gap between "AI-generated code" and "developer-written code" is narrowing with each model generation.

Conclusion

Prompt engineering for code generation is a discipline that bridges software engineering expertise with AI communication skills. The quality of generated code is a direct function of the specificity, structure, and context you provide. By following the CTC framework — Context, Task, Constraints — and providing concrete examples of the desired output pattern, you can generate production-quality code that integrates seamlessly with your codebase.

Key takeaways from this guide:

  1. Specificity beats verbosity — a precise prompt with types and examples outperforms a long, vague description every time.
  2. Few-shot examples are the strongest tool — showing the model one complete, correct implementation establishes the pattern it will follow.
  3. Chain-of-thought reasoning improves complex generation — asking the model to plan before coding produces better architecture and edge case handling.
  4. Explicit constraints prevent common failures — specifying what not to do is as important as specifying what to do.
  5. Always validate generated code — run type checks, linters, and tests before merging AI-generated code into your codebase.

Start by creating a prompt template library for your team's most common generation tasks — CRUD operations, API endpoints, test suites, and component templates. Refine these templates based on the quality of output they produce, and share the best-performing prompts across your team. The OpenAI prompt engineering guide and Anthropic's documentation provide additional frameworks and best practices.