Lesson 10

Lesson 10

47 min
August 30, 2026
Step 1 of 7

Chapter 1

Why Precision in Code Prompts Matters More Than You Think

Most developers who start using AI assistants for coding make the same mistake: they ask for something vague like "write a function to sort a list" and then spend twenty minutes debugging the result. The AI returns a generic bubble sort, or a function that mutates the input when you wanted a new list, or code that works in Python 3.8 but breaks in 3.12. The problem isn't the AI — it's the prompt.

A precise code prompt is a specification. When you write a prompt for code generation, you are not having a conversation; you are writing a mini-requirements document. The AI will happily fill in every gap you leave, and it will fill those gaps with its own assumptions. Those assumptions are often wrong for your specific context.

Consider what happens when you ask for "a function to parse a CSV file." The AI might use the csv module (good), but it might also assume the file has a header row, assume comma delimiters, assume no quoted fields, and return a list of dictionaries. If your actual file uses semicolons and has no header, you now have to rewrite the generated code. A precise prompt would have specified the delimiter, the presence or absence of a header, the expected output format, and the error handling behavior.

The cost of imprecision compounds. Every assumption the AI makes that doesn't match your reality is a bug you must find and fix. With a precise prompt, you shift the debugging burden from your code to your specification — which is exactly where it belongs.

Anatomy of a Precise Code Prompt

A well-formed code generation prompt contains five essential components. Missing any one of them invites the AI to improvise.

1. Language and Version

State the programming language explicitly, including the version if it matters. "Python" is not enough — Python 2 and Python 3 have different syntax. "Python 3.11+" tells the AI it can use modern features like match statements or tomllib.

2. Framework and Libraries

If you want a specific framework, name it. "Use Flask, not Django" or "Use Express 4.x" prevents the AI from choosing its default. If you want no external dependencies, say so explicitly.

3. Input and Output Specification

Describe exactly what goes in and what comes out. Include data types, structures, and edge cases. "Input: a list of integers. Output: a new list of integers, sorted ascending, original list unchanged." This level of detail eliminates ambiguity.

4. Error Handling Requirements

Tell the AI what should happen when things go wrong. Should it raise an exception? Return None? Log a warning? Return a default value? Each choice has different implications for the calling code.

5. Constraints and Style

Mention performance requirements, naming conventions, or documentation expectations. "Include a docstring in Google style" or "Must run in O(n log n) time" are constraints the AI can honor if you state them.

Worked Example: A Python Function with Docstring

Let's build a precise prompt step by step. We need a function that calculates the median of a list of numbers. Here's the vague version most people would write:

Write a function to calculate the median of a list.

And here's the precise version:

Write a Python 3.11 function named calculate_median that accepts a list of
floats or integers. The function must:

- Return the median as a float.
- Handle both odd and even length lists correctly.
- Raise a ValueError if the input list is empty.
- Raise a TypeError if any element is not a number (int or float).
- Not modify the original list.
- Include a docstring in Google style that describes parameters,
  return value, and exceptions raised.
- Use only the standard library.

Example behavior:
calculate_median([3, 1, 2]) -> 2.0
calculate_median([1, 2, 3, 4]) -> 2.5
calculate_median([]) -> raises ValueError
calculate_median([1, "a"]) -> raises TypeError

Notice what this prompt specifies: the exact function name, the version, the input type, the output type, three distinct edge cases, the mutation constraint, the docstring style, and the library constraint. The AI has no room to improvise. Here's the output you can expect:

def calculate_median(numbers):
    """Calculate the median of a list of numbers.

    Args:
        numbers: A list of int or float values.

    Returns:
        The median value as a float.

    Raises:
        ValueError: If the input list is empty.
        TypeError: If any element is not an int or float.
    """
    if not numbers:
        raise ValueError("Input list cannot be empty")

    for num in numbers:
        if not isinstance(num, (int, float)):
            raise TypeError(f"Element {num} is not a number")

    sorted_numbers = sorted(numbers)
    n = len(sorted_numbers)
    mid = n // 2

    if n % 2 == 0:
        return (sorted_numbers[mid - 1] + sorted_numbers[mid]) / 2.0
    else:
        return float(sorted_numbers[mid])

This code is correct, handles all specified edge cases, and follows the requested docstring format. The prompt did the heavy lifting.

Step-by-Step Application: Building a REST API Endpoint in Node.js

Now let's apply the same discipline to a more complex task. We'll write a prompt to generate a REST API endpoint in Node.js using Express. Follow these steps literally.

Step 1: Define the Environment

Open your terminal and create a new project directory. Run npm init -y to create a package.json file. Then install Express with npm install express. This gives you a real environment to test the generated code.

Step 2: Write the Prompt

Create a new file called prompt.txt and paste the following:

Write a Node.js Express 4.x route handler for a POST endpoint at
/api/users. The request body will be JSON with the following structure:

{
  "name": "string, required, max 100 characters",
  "email": "string, required, must be a valid email format",
  "age": "integer, optional, must be between 18 and 120"
}

The handler must:

- Validate the request body. If validation fails, respond with
  status 400 and a JSON object: {"error": "description of the first
  validation error"}.
- If validation passes, create a user object with an id field
  (use a simple counter starting at 1) and the provided fields.
- Store the user in an in-memory array.
- Respond with status 201 and the created user object as JSON.
- Use express.json() middleware for parsing.
- Export the router using module.exports.
- Include JSDoc comments for the route handler.

Assume the Express app is already configured and will mount this
router. Do not include the app.listen() call.

Step 3: Generate and Review

Paste this prompt into your AI assistant. The output should look something like this:

const express = require('express');
const router = express.Router();

let users = [];
let nextId = 1;

/**
 * POST /api/users
 * Creates a new user.
 * @param {Object} req - Express request object
 * @param {Object} res - Express response object
 */
router.post('/api/users', (req, res) => {
    const { name, email, age } = req.body;

    // Validate name
    if (!name || typeof name !== 'string' || name.length > 100) {
        return res.status(400).json({ error: 'name is required and must be a string of max 100 characters' });
    }

    // Validate email
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!email || typeof email !== 'string' || !emailRegex.test(email)) {
        return res.status(400).json({ error: 'email is required and must be a valid email address' });
    }

    // Validate age
    if (age !== undefined) {
        if (typeof age !== 'number' || age < 18 || age > 120) {
            return res.status(400).json({ error: 'age must be a number between 18 and 120' });
        }
    }

    const newUser = { id: nextId++, name, email, age };
    users.push(newUser);
    res.status(201).json(newUser);
});

module.exports = router;

Step 4: Test the Code

Create a file called app.js with the following content to mount and test the router:

const express = require('express');
const userRouter = require('./userRouter');

const app = express();
app.use(express.json());
app.use(userRouter);

app.listen(3000, () => console.log('Server running on port 3000'));

Save the generated router as userRouter.js. Run node app.js in your terminal. Then test with curl:

curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "email": "alice@example.com", "age": 30}'

You should receive a 201 response with the created user object. Try sending invalid data — a missing name, a bad email, an age of 15 — and verify you get 400 responses with descriptive error messages.

Expert Tip

When you specify error handling in your prompt, always include the exact error message format. In the example above, we specified {"error": "description"}. This matters because your frontend or API consumers will likely parse that error object. If the AI generates a different structure — say, {"message": "..."} or a plain string — your client code will break. By locking down the error contract in the prompt, you ensure the generated code matches your existing error-handling infrastructure. This is the difference between code that works in isolation and code that works in your system.

Common Mistakes

Common Mistakes in Code Prompts

  • Omitting the version: "Use Python" leads to code that may use deprecated syntax. Always specify "Python 3.11+" or "Node.js 18+".
  • Not specifying mutation behavior: If you don't say "do not modify the input," the AI may write a function that sorts in place, silently changing your data.
  • Vague error handling: "Handle errors gracefully" means nothing. Specify the exact status code, the response body, and the condition that triggers each error.
  • Forgetting the output format: If you don't say "return a JSON object with fields X and Y," the AI will invent its own structure.
  • Asking for too much in one prompt: A single prompt for "a full CRUD API with authentication, pagination, and logging" will produce shallow code. Break it into separate prompts for each endpoint or concern.

Practice Task: Your Turn

Write a prompt that generates a Python 3.12 function named flatten_dict that takes a nested dictionary and returns a flattened version where nested keys are joined with a dot. For example, {"a": {"b": 1, "c": {"d": 2}}} becomes {"a.b": 1, "a.c.d": 2}.

Your prompt must specify:

  • The exact function name and signature.
  • That the input is a dictionary with string keys and values that may be dictionaries or any other type.
  • That the output is a new dictionary (do not modify the input).
  • That non-dictionary values are copied as-is.
  • That empty dictionaries should be flattened to an empty string key (e.g., {"a": {}} becomes {"a.": {}} — or decide on a different behavior and specify it).
  • A docstring in reStructuredText format.
  • Three example input/output pairs.

After generating the code, test it with at least five different inputs, including a deeply nested structure, an empty dictionary, and a dictionary with non-string keys (which should raise a TypeError — specify that in your prompt too).

Self-verification: your function should pass all your test cases, and the docstring should be complete enough that another developer could use the function without reading the source code.

This task should take under 15 minutes. The goal is not just to get working code — it's to get code that matches your specification exactly, with no surprises.

Loading ratings...