Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
18 minLesson 12 of 18
Coding with ChatGPT

Code Generation: Best Practices

Code Generation with ChatGPT

ChatGPT writes good code — but the quality of what you get depends heavily on how you ask. Vague requests produce vague code. Specific, context-rich requests produce code you can actually use. This lesson covers how to prompt for code effectively, what to verify before running it, and how to integrate ChatGPT into a real development workflow.

The Mindset: ChatGPT as a Pair Programmer

Don't think of ChatGPT as a magic code generator. Think of it as a skilled pair programmer who:

  • Writes quickly and without ego
  • Knows a broad range of languages and frameworks
  • Sometimes makes confident mistakes
  • Gets much better with good context
  • Needs you to review and test their work

Your job is to specify clearly, review carefully, and test thoroughly.

The Code Generation Prompt Structure

Context: [language, framework, environment]
Task: [what the code needs to do — be specific]
Constraints: [style guide, patterns to follow, what to avoid]
Input/Output: [what goes in, what should come out]
Example: [optional — show an example if helpful]

Weak vs. Strong Prompts

❌ Weak: "Write a function to send emails"

✅ Strong: 
Write a TypeScript function that sends transactional emails using 
the Resend SDK. 

Requirements:
- Function signature: sendEmail(to: string, subject: string, html: string): Promise<void>
- Uses the Resend SDK (already installed)
- Reads RESEND_API_KEY from environment variables
- Throws a descriptive error if the send fails (don't swallow errors)
- No try/catch — let errors bubble up to the caller

We're using strict TypeScript, no any types.

The second prompt gives ChatGPT everything it needs to write code you can actually use.

Common Code Generation Use Cases

Utility functions

Write a TypeScript utility function: formatCurrency(amount: number, currency: string): string

It should:
- Format numbers as currency strings (e.g., 1234.5 → "$1,234.50")
- Support USD, EUR, GBP, and JPY
- Use Intl.NumberFormat (built-in, no external libraries)
- Handle negative amounts correctly
- Throw if an unsupported currency is passed

Include JSDoc with an example.

API integration

Write a TypeScript module that wraps the Stripe API for our use case.

We need three functions:
1. createPaymentIntent(amount: number, currency: string): Promise<PaymentIntent>
2. retrievePaymentIntent(id: string): Promise<PaymentIntent>
3. cancelPaymentIntent(id: string): Promise<void>

Context:
- Using Stripe SDK v14
- API key from process.env.STRIPE_SECRET_KEY
- All amounts are in the smallest currency unit (cents for USD)
- TypeScript strict mode

Don't write a class — export named functions.

Database queries

Write a Prisma query for this use case:

Get all published courses with:
- The course details (id, title, slug, description, price)
- The instructor name and avatar
- The count of enrolled students
- The average rating from reviews
- The 3 most recent reviews (reviewer name, rating, comment)

Filter: Only courses where isPublished = true
Sort: By enrollment count descending
Limit: 20 per page with cursor-based pagination

Current Prisma schema: [paste relevant schema sections]

React/Next.js components

Write a React component: <DataTable> 

Props:
- data: T[] (generic)
- columns: Column<T>[] where Column has: key, header, render?: (row: T) => ReactNode
- isLoading?: boolean (show skeleton rows)
- emptyMessage?: string (shown when data is empty)
- onRowClick?: (row: T) => void

Requirements:
- TypeScript generic component
- Skeleton: 5 rows with animated pulse while loading
- Responsive: horizontally scrollable on mobile
- Tailwind CSS for styling
- No external table libraries

Data transformation

Write a Python function to transform this data structure.

Input format (list of records):
[
  {"date": "2024-01-15", "category": "Revenue", "amount": 5200},
  {"date": "2024-01-15", "category": "Expenses", "amount": 3100}
]

Output format (date-keyed dict):
{
  "2024-01-15": {
    "revenue": 5200,
    "expenses": 3100,
    "profit": 2100
  }
}

Handle:
- Multiple entries for the same date and category (sum them)
- Dates with only revenue or only expenses (the other defaults to 0)
- Type hints throughout

Giving ChatGPT Your Existing Code

The most accurate code generation happens when ChatGPT can see your existing patterns:

Here is our existing API route handler pattern:
[paste an existing route handler]

Write a new route handler following this exact same pattern for:
- Endpoint: POST /api/courses/:id/enroll
- Auth: User must be logged in
- Logic: Create an enrollment record in the database, prevent duplicate enrollments
- Response: Return the enrollment record

Use the same error handling, response format, and middleware as the example.

Providing an existing example is more powerful than describing your patterns in words.

Asking for Multiple Approaches

When you're not sure which approach is best:

I need to implement rate limiting for our API routes in Next.js.

Show me two approaches:
1. Using Upstash Redis with a sliding window
2. Using a simple in-memory store (for single-server deployments)

For each: show the implementation, explain the tradeoffs, and tell me when to choose it.

Generating Tests Alongside Code

Always ask for tests when generating application code:

Write the function AND write the tests for it.

Function: parseCSV(input: string): Record<string, string>[]

Test cases to cover:
- Standard CSV with headers
- CSV with quoted fields containing commas
- Empty input
- Input with only headers (no data rows)
- Inconsistent column counts (throw an error)

Use Vitest. Tests should be specific about what they're testing.

Code Review Prompts

Use ChatGPT to review code you've written:

Review this code for:
1. Bugs or edge cases I haven't handled
2. Security issues (especially: SQL injection, XSS, authentication gaps)
3. Performance problems
4. Any place where the code could fail unexpectedly in production
5. Things that are unnecessarily complex and could be simplified

Be direct — don't soften feedback. I want to find problems before production does.

[Your code]

Explaining Unfamiliar Code

When you encounter code you didn't write:

Explain this code to me, section by section.

I know [language] reasonably well but I'm unfamiliar with [framework/library].

For each section, explain:
1. What it's doing
2. Why it might be done this way (design decision)
3. Any non-obvious gotchas

[Paste code]

What to Always Verify

Before running any AI-generated code:

  1. Read it line by line — don't run code you don't understand
  2. Check imports and dependencies — are these actually installed?
  3. Verify error handling — does it fail gracefully?
  4. Check for security issues — is user input properly validated?
  5. Test with real data — especially edge cases and invalid input

ChatGPT gets wrong most often:

  • Specific API methods that changed between library versions
  • Edge cases in complex logic
  • Security-sensitive code (auth, crypto) — always get a second review
  • Performance characteristics of algorithms at scale

The Iterative Refinement Loop

Rarely is the first response exactly what you need. Work iteratively:

The function works but it's too slow for large datasets. 
It currently iterates through the array twice. 
Refactor it to do this in a single pass without changing the external interface.
This works but it doesn't match our project's code style. 
In our codebase, we:
- Use const for variables that don't change
- Use named exports, not default exports
- Put types in a separate types.ts file
- Write functions as const arrow functions, not function declarations

Refactor to match these conventions.

Next lesson: Debugging code — using ChatGPT to diagnose and fix errors.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!