Lesson 3 of 5 · 15 min

Lesson 3: Prompting for Code (Not Chat)

Course: Coding with AI Assistants

Estimated time: 25 minutes

Difficulty: Beginner-Intermediate Builder


You've used ChatGPT to write an email or explain a concept. Prompting for code is not that. The techniques that make a chat prompt good make a code prompt mediocre. And the techniques that make a code prompt good are things almost nobody explains.

This lesson fixes that.


Why Code Prompting Is Different

When you prompt a chat AI, you want a response. The AI gives you text. You read it. Maybe you act on it.

When you prompt a code AI, you want executable output that works correctly in your specific environment, with your specific dependencies, in your specific file structure. The gap between "AI produces code" and "code works in your project" is where most people lose time.

Chat prompting aims for clear communication. Code prompting aims for complete specification.

The difference in practice:

Chat prompt (for explanation): "How do I handle authentication in a Next.js API route?"

You get an explanation with maybe a code example. Good for learning.

Code prompt (for implementation): "Write a Next.js App Router middleware that protects all routes under /dashboard. Use Clerk for auth. If the user isn't authenticated, redirect to /sign-in. If they are authenticated, allow the request through. The Clerk middleware helper is already installed at node_modules. Use clerkMiddleware and createRouteMatcher from @clerk/nextjs/server."

You get code you can paste into your project and have working in 90 seconds. Those are different prompts for different goals.

Know which one you need before you start typing.


The Three Ingredients of a Good Code Prompt

Every effective code prompt has three things: context, a clear request, and constraints.

1. Context: What the AI needs to know about your environment

This is what most people skip. They ask the AI to write code without telling it anything about the code it's going to live next to.

Context includes:

  • Framework and version (Next.js 14 App Router, not just Next.js)
  • Language and runtime (TypeScript, Node 20)
  • Libraries already in the project (Supabase client initialized at lib/supabase.ts)
  • Existing patterns it should follow (auth is handled via Clerk, error handling uses a custom AppError class)
  • File paths for files it needs to interact with

You don't need to paste your entire codebase. You need to tell the AI what it doesn't know but needs to know.

2. Clear request: What you actually want

Be specific about what type of code you want. "A function," "a React component," "an API route," "a database migration," "a test file for this function." Vague requests get vague output.

Also specify what the code should do in concrete terms, not abstract ones.

Vague: "Handle the payment flow"

Concrete: "Write an API route at /api/webhooks/stripe that handles checkout.session.completed Stripe webhook events. On success, update the user's subscription_tier in the profiles table to 'pro' and set subscription_expires_at to 30 days from now."

3. Constraints: What the code should not do or what to avoid

This is where you prevent the AI from solving the problem correctly but wrong.

Examples:

  • "Don't add a try/catch wrapper - error handling is done at the middleware level"
  • "Don't import from @supabase/auth-helpers-nextjs - we use @supabase/ssr"
  • "Keep the response format consistent with the existing routes - return { data, error } not a bare value"

Constraints aren't limitations on the AI. They're information about your project's patterns that the AI doesn't have. Including them reduces the probability of output that technically works but doesn't fit.


The Brief-in-File Pattern

For anything more complex than a single function, stop prompting in chat. Write a brief file.

A brief file is a markdown file in your project that describes what you want built. You point your AI editor at it. The AI reads the full spec and executes it.

Why this works better than chat:

  • You can take time writing the spec. No pressure to get it right in a chat message before you hit Enter.
  • The spec is part of your codebase. Other people (or your future self) can read it.
  • You can revise the spec and re-run the AI without losing context.
  • The AI reads the whole spec at once, not in fragments. Better coherence in the output.

A brief file for a feature looks like this:


# Brief: User Profile Page

## Goal
Display a user's profile with their name, avatar, plan tier, and usage statistics.

## Route
GET /profile
- Protected route (requires Clerk auth)
- If user doesn't have a profile row in Supabase, create one on first visit

## Components
ProfilePage (/app/profile/page.tsx)
- Fetches user data server-side using Supabase server client
- Renders ProfileHeader and UsageStats components

ProfileHeader (/components/ProfileHeader.tsx)
- Props: { name: string, email: string, avatarUrl: string, plan: 'free' | 'pro' }
- Shows avatar, name, email, and a badge for the plan tier

UsageStats (/components/UsageStats.tsx)
- Props: { requestsThisMonth: number, requestsLimit: number }
- Shows a progress bar: requestsThisMonth / requestsLimit
- Shows the numbers below the bar

## Data
Supabase table: profiles
- user_id (text, primary key, matches Clerk user ID)
- full_name (text)
- avatar_url (text, nullable)
- plan (text, default 'free')
- requests_this_month (integer, default 0)

Supabase client: use the server client from lib/supabase-server.ts

## Not in This Feature
- Edit profile functionality
- Plan upgrade flow
- Billing history

That brief is about 250 words. The resulting feature would take a typical developer 2-4 hours to build from scratch. With the brief and a good AI editor, it's 30-60 minutes of generation plus review.

The 250-word investment is worth it every time.


Context Management: What to Include, What to Skip

Context window isn't infinite. You don't want to dump your entire codebase into every prompt. You want to include the right context.

Include:

  • The specific files the new code will import from or be imported by
  • The type definitions for any types the new code uses
  • A short example of an existing similar feature ("here's how the /api/analyze route works, write /api/summarize the same way")
  • Error messages in their entirety (never paraphrase an error when asking for a fix)
  • The actual function or component that's broken (not your description of it)

Skip:

  • Files that have nothing to do with the task
  • Long comments and documentation that don't affect the types or logic
  • Test files when you're building the feature (they create noise)
  • Config files that aren't relevant (tsconfig, eslint config, etc.)

The context test: If you removed this piece of context and the AI could still produce correct code, you don't need it. Include context that changes what the correct answer looks like.


Iterative Refinement vs. Getting It Right the First Time

There's a temptation to keep prompting until you get perfect output in one shot. That's not how this works, and trying to make it work that way wastes time.

A better workflow: get 80% right in the first pass, then iterate specifically on what's wrong.

First pass: give the full spec, get code that mostly works.

Then, for each thing that's wrong or missing, make a specific targeted request:

  • "The form doesn't clear after submission. Add a reset() call after the API call succeeds."
  • "The error state doesn't show to the user. Add a toast notification when the API returns an error."
  • "The loading state is applied to the whole form. Apply it only to the submit button."

Specific, targeted refinements are faster than trying to describe everything perfectly upfront. They're also easier to review, because each change is small and isolated.


When the AI Gets It Badly Wrong

Sometimes the AI produces code that misses the point so completely that starting over is faster than fixing it. Signs this is happening:

  • The code references imports or functions that don't exist in your project
  • The code uses an entirely different approach than the one you specified
  • The code is syntactically correct but logically does something different from what you described

When this happens, don't patch the bad output. Close it. Go back to your spec. Read your spec again. Figure out what was ambiguous and make it specific. Then regenerate from the spec.

Most "AI got it completely wrong" situations trace back to a spec that was missing a key piece of context or had an ambiguous instruction. The AI made a reasonable interpretation that wasn't the interpretation you had in mind.

Fix the spec, not just the code.


Prompt Templates That Actually Work

New feature from scratch:


I'm building [feature name] in my [framework] project.

Project context:
- [Key library A] is set up at [path]
- [Key library B] is set up at [path]
- We follow [pattern X] for [Y type of code]

What I want:
[Precise description of what the feature does - user flow, inputs, outputs]

Files to create:
- [file path] - [what it contains]
- [file path] - [what it contains]

Don't include: [things to explicitly exclude]

Bug fix:


This function is producing an incorrect result.

Function:
[paste the function]

Expected behavior: [what it should do]
Actual behavior: [what it's doing instead]
Error (if any): [paste the full error]

Project context: [any relevant info about the environment]

Fix the bug. Explain what was wrong in one sentence.

Test generation:


Write tests for this function using [testing framework].

Function:
[paste the function]

Test these cases:
- Happy path: [describe]
- Edge case 1: [describe]
- Edge case 2: [describe]
- Error case: [describe]

Use [mocking approach] for [external dependencies].

Exercise: Write a Brief and Execute It

Pick a feature you need to build in the next week. Could be in a side project, a work project, or even something hypothetical if you don't have a current project.

Write a brief file for it using the structure above. Be specific enough that a developer who's never seen your project could read the brief and know exactly what to build.

Then open your AI editor and tell it to implement the brief. Point it at the file.

Review the output against the brief. For each piece of the brief, check: did the AI implement this? Is the implementation correct?

Write down the gap between what you asked for and what you got. Then write targeted follow-up requests to close each gap.

After the exercise, you should have a working sense of how tight your briefs need to be to get output that needs minimal correction.


Key Takeaway

Code prompting isn't about clever phrasing - it's about complete specification. The more precisely you describe context, request, and constraints, the less time you spend correcting AI output.


What's Next

You've got the prompting fundamentals. Lesson 4 tackles the harder problem: how do you work effectively with AI on multi-file projects when the codebase is bigger than what fits in a context window?