Tutorial 6: Prompting for Code Generation
- Contributor
- 4 days ago
- 3 min read
LLMs write impressive code in demos. Production code generation needs more discipline. This tutorial walks through the patterns.
What You'll Build
Code generation prompts that produce working, testable, debuggable code.
Step 1: Specify the Context (15 min)
The model needs to know:
Language and version
Framework and version
Code style preferences
Existing patterns in the codebase
What it's integrating with
You are writing TypeScript for a Node.js 20 backend using Express 4.18.
Code style:
- ES modules (import/export)
- Async/await over .then()
- Named exports preferred
- Functional style where natural
- Comments only for non-obvious logic
The code integrates with our existing user service (signatures shown below).
Generic prompts produce generic code. Specific context produces fitting code.
Step 2: Show Examples of the Codebase (15 min)
Include 1-2 existing functions:
Here are examples of how we structure handlers:
import { Request, Response } from 'express';
import { userService } from '../services/userService.js';
export const getUserHandler = async (req: Request, res: Response) => {
const userId = req.params.id;
const user = await userService.findById(userId);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
return res.status(200).json(user);
};
The model patterns its output. Style consistency improves dramatically.
Step 3: Be Specific About the Task (10 min)
Bad: "Write a function to handle user updates."
Good: "Write an Express handler for PATCH /api/users/:id. It should:
Validate the request body matches the UserUpdate schema
Return 400 if validation fails
Return 404 if user doesn't exist
Return 200 with the updated user on success
Use the existing userService.update(id, updates) method
Handle errors with the existing errorHandler middleware"
Detailed prompts produce detailed code.
Step 4: Specify the Tests (15 min)
Ask for tests too:
Also write tests for this handler using vitest and supertest.
Cover:
- Successful update (200)
- Validation failure (400)
- Non-existent user (404)
- Authentication required (401)
Tests come for free. Often the model writes good tests; verify they work.
Step 5: Validate the Output (15 min)
LLMs hallucinate. For code:
Run it. Does it execute?
Does it type-check?
Does it pass tests?
Does it actually solve the stated problem?
Each is a check. Don't trust output you haven't verified.
Step 6: Iterate on Failures (varies)
When generated code is wrong:
Paste the actual error back
Ask for the specific fix
Don't just regenerate from scratch
The code you wrote has this TypeScript error:
"Property 'id' does not exist on type 'Request<ParamsDictionary>'"
Fix this specific error.
Targeted iteration is faster than starting over.
Step 7: Watch for Common Patterns to Avoid (10 min)
LLM-generated code often has:
Outdated patterns (Node.js v12 patterns when you're on v20)
Made-up library APIs (functions that don't exist)
Insecure patterns (SQL injection, XSS)
Over-defensive code (try/catch everything; null checks for impossible cases)
Inconsistent style with your codebase
Each is a thing to catch in review.
Step 8: Use for Boilerplate, Not Architecture (5 min)
LLMs are best at:
CRUD handlers
Test scaffolding
Data transformation
Validation
Refactoring (when you tell them what to refactor)
LLMs are worst at:
System design decisions
Non-obvious business logic
Performance optimization
Cross-cutting concerns (auth, observability)
Use LLMs for the easy stuff; reserve human time for the hard.
Step 9: Refactoring With LLMs (15 min)
Refactoring tasks where LLMs help:
Refactor this function to:
- Use Result<T, Error> return type instead of throwing
- Add type annotations
- Extract the validation logic into a separate function
- Maintain identical behavior (don't change the logic)
[function code]
Specific refactoring requests produce useful outputs. Vague "improve this code" produces unpredictable changes.
Step 10: Review Carefully (always)
LLM-generated code passes type-checks and tests but still needs review:
Is it solving the right problem?
Does it match codebase conventions?
Is it secure?
Does it handle the edge cases?
Will the next engineer understand it?
Code that works isn't the same as code that's good. Review like any other PR.
What You Just Did
You set up prompts that produce usable code. With context, examples, specific tasks, and tests, the output quality rises dramatically.
Common Failure Modes
No context. Generic code that doesn't fit your codebase.
No verification. Code that "looks right" but doesn't actually run.
Trust without review. Hallucinations slip through.
No iteration. First output is final, even when wrong.
Architecture decisions to LLM. Hand the wrong question to the wrong tool.


