Specifying What You Want -- Turning Intent into Prompts
Learning Objectives
- You can write a precise function specification covering name, parameters, return value, and behavior on edge cases.
- You can recognize when an AI prompt is too vague to produce reliable output.
- You can use a function specification as the basis for an AI prompt, and verify the result against the specification.
In the first part of the course, the AI-collaboration skill was reading: understanding what a piece of code actually does. In the second part of the course, the skill was verifying: confirming that what it does is what it should do, on every input it might see. Both of those skills are about evaluating code that already exists.
In this part, the skill is specifying: writing a precise description of what you want before the code exists. This is a different skill, because it is about being precise before the code is written, rather than after. It is also a more fundamental skill, because it is the skill of making design decisions and communicating them clearly, which is the foundation for writing code that matches your intent, whether you write it yourself or ask an AI to write it for you.
Writing Versus Specifying
When you write code yourself with a vague idea in mind, you fill in the gaps as you go. You decide that 15 minutes should be “short” instead of “solid” because that is what feels right when you reach the line. You decide that an empty list should return None rather than 0 or raising an exception because the moment of writing reminds you that one of them might crash later. The vagueness is fine, because you are the one resolving it, in your head, as you’re working on the code.
When an AI writes code from a vague idea, it fills in the gaps. The AI decides that 15 is solid because the most common style across its training data leans one way. It decides that empty input returns 0 because that was the most common pattern in similar examples. None of these decisions are yours. None of them are even explicit.
This is fine if you happen to want what the AI happened to choose. It is not fine if you wanted something specific. And in the typical case — where you have not really thought about the choice — it is even worse than not fine, because you do not realize a choice was made until something goes wrong.
The fix is to make the choices before the code exists. A specification is a document that outlines those choices in writing.
What a Function Specification Looks Like
A function specification has five parts. The last one is optional for functions that never raise, but for any function with input validation it should be present.
| Part | Question it answers |
|---|---|
| Name | What is this function called? |
| Parameters | What does it take, in what order, with what types? |
| Return value | What does it produce? What type? |
| Behavior on edge cases | What does it do on the empty input, the boundary value, the unusual case? |
| Raises (when applicable) | Which exceptions does it raise, and on what inputs? |
The first three are familiar territory. The fourth is where most of the work happens, and it is also where most function specifications fall short. A spec that says “returns the maximum” and does not say what happens on an empty list is a spec that has only done half its job. The fifth is the natural home for any input-validation decisions you made using Exceptions and Input Validation’s exception-raising pattern: if the function raises ValueError on bad input, the spec needs to say so.
Here is a complete specification for classify_session:
classify_session(minutes: int) -> str
Returns a one-word description of a session length.
"short" for minutes < 15 "solid" for 15 <= minutes <= 90 "long" for minutes > 90
Edge cases: - Negative minutes: treated as short. - Zero minutes: treated as short. - Exactly 15: solid. - Exactly 90: solid. - Very large minutes (e.g., 10000): treated as long.This is what a precise spec looks like. There are no places where two reasonable programmers would disagree about what the function should do, because the spec has pinned everything down.
Now compare the above to a more informal description: “a function that classifies study sessions by length.”
The informal version does not say what the thresholds are. It does not say what happens at the boundaries. It does not say what the return value looks like — is it "short", or "Short", or 1, or something else? Two programmers reading the informal description would produce two different functions, and neither would technically be wrong, as the description is vague.
Edge Cases Matter
Look at the spec for classify_session again. The edge case section is the longest part. That is on purpose.
The “normal” behavior of a function is usually indicated by the name of the function. Almost any reasonable programmer or AI would write something like if minutes < 15: return "short" for the short case. There is no real disagreement to resolve.
The disagreements are at the edges. Is 15 short or solid? Is 90 solid or long? What does the function do when minutes is 0, or negative, or absurdly large? These are the choices that affect behavior in ways that the obvious tests do not catch. They are also the choices an AI will make for you if you do not make them yourself.
This is the same checklist from the chapter Verifying Behavior and Finding the Edge Cases AI Misses from the previous part, applied at a different point in the process:
| Case | Where it shows up |
|---|---|
| Empty | Spec section for empty list or empty string |
| Single element | Spec section for a list with exactly one item |
| Boundary values | Spec section for exact threshold values and < vs <= |
| All equal | Spec section for a list where every item is the same |
| No match | Spec section for what happens when a search finds nothing |
| Zero | Spec section for 0 as a numeric input |
| Negatives | Spec section for negative numeric inputs |
| Unusual input format | Spec section for Done vs done, whitespace, etc. |
In the previous part, you asked: “what inputs would break this code?” Now you ask: “what should this code do on each of those inputs?”
Vague vs. Specific Prompt
To make the difference concrete, here are two prompts you might give to an AI assistant.
Vague prompt:
Write a Python function that classifies study sessions by length.
What might the AI produce?
def classify_session(minutes): if minutes < 30: return "Short" elif minutes < 60: return "Medium" else: return "Long"That runs. It looks fine. Is it what you wanted? Almost certainly not. Compare with your intended thresholds:
- The AI used
30and60as thresholds. You wanted15and90. - The AI used three labels:
Short,Medium,Long. You wantedshort,solid,long(capitalization included). - The AI picked
<everywhere, so30is medium and60is long. Without a spec, this is no better or worse than any other choice — but it is a choice that you did not get to make.
Specific prompt:
Write a Python function
classify_session(minutes: int) -> strthat returns"short"forminutes < 15,"solid"for15 <= minutes <= 90, and"long"forminutes > 90. Treat negative or zero minutes as short. Exactly 15 should be solid; exactly 90 should be solid.
What might the AI produce now?
def classify_session(minutes): if minutes < 15: return "short" elif minutes <= 90: return "solid" else: return "long"This is exactly what you wanted, because there was nowhere for the AI to make decisions. The specification did the design work, and the AI did the typing.
Prompt a precise reading classifier
0 / 10 points
Write a prompt that asks a large language model to create Python code only.
The generated program must define a function named reading_label(minutes) with this exact behavior:
- return
"warmup"for minutes less than 20, - return
"focused"for minutes from 20 through 60, - return
"marathon"for minutes greater than 60, - treat negative minutes and zero minutes as
"warmup", - and include no
input(...)calls.
The generated code will be tested by calling:
reading_label(-5) # should return "warmup"reading_label(0) # should return "warmup"reading_label(19) # should return "warmup"reading_label(20) # should return "focused"reading_label(60) # should return "focused"reading_label(61) # should return "marathon"Your prompt should explicitly include the exact function name, exact returned strings, exact boundary values, and the instruction to avoid input(...).
Ask for Python code only, with no explanation.
Specification as a Design Activity
Look back at what just happened. To write the specific prompt above, you had to know the thresholds. You had to decide what to do at the boundaries. You had to pick the exact strings to return. By the time the spec was written, the important design decisions were already made. The code that followed was almost mechanical.
Creating specifications is programming. The act of writing a spec is the act of making the decisions that the program embodies.
This is also true when you are not using an AI. A function with a clear spec in your head — or, perhaps, in a comment above the function — is faster to write, easier to test, and less likely to be wrong. The discipline that makes AI prompts effective is the same discipline that makes your own code better. The AI just happens to make the difference visible.
Explain specification as design
0 / 10 points
In your own words, explain why writing a function specification is part of programming.
Use this function idea as your example: a function that returns the average of a list of minutes.
In 3-6 sentences, mention at least two design decisions a specification would need to make, such as empty-list behavior, return type, invalid inputs, or exceptions.
A Workflow
Putting the ideas together, here is a workflow for using specifications, with or without an AI:
- Write the spec. Five parts: name, parameters, return, edge cases, and raises (when applicable). The edge case section should explicitly cover the rows of the checklist that apply.
- Decide cases from the spec. The cases you would test for already came out of the spec. Write them down before writing the function.
- Implement. Either you write the function, or you ask an AI to write it. The spec is the input to either.
- Test against the spec. Write the test assertions from the cases in step 2. These tests check that the function matches the spec, not that it matches itself. This is the key distinction.
- Compare. Run the tests. If they pass, the function matches what you wanted. If they fail, you know which case it failed on.
Take a moment to re-read step 4. There is a temptation to write tests that match whatever the implementation happens to do — read the code, then write tests that match. That is testing the implementation against itself. Tests derived from the spec catch the case where the implementation drifted from the intent. That is the case you most want to catch. Pay explicit attention to testing the edge cases, because those are the cases where the behavior of the function should be tested.
Tests from the spec
0 / 5 points
A spec says:
average_score(scores: list of int) -> float or None
Returns the arithmetic average of the scores.
Edge cases: - Empty list: returns None. - Single-element list: returns that element. - Negative scores are included in the average.Which group of assertions best tests the function against this spec?
A Worked Example: longest_session
To put it all together, here is a complete spec for the longest_session function from the previous chapter.
longest_session(minutes_list: list of int) -> int or None
Returns the largest value in `minutes_list`.
Edge cases: - Empty list: returns None. - Single-element list: returns that element. - All elements equal: returns that element. - List contains negative numbers: returns the largest (closest to zero). - List of length 1 with value 0: returns 0.Notice what the spec is doing. It is explicitly choosing to return None for the empty case, rather than crashing, returning 0, or raising an error. Each of those would be defensible; the spec picks one. It is explicitly saying that negatives are handled (a programmer or AI might otherwise assume non-negative input and write a function that breaks on negatives). It is being clear about the return type: an integer, or None.
Now derive the tests from the spec:
assert longest_session([]) is Noneassert longest_session([45]) == 45assert longest_session([60, 60, 60]) == 60assert longest_session([-5, -10, -3]) == -3assert longest_session([0]) == 0Five tests, each corresponding to a line of the spec. If the function passes all five, it matches the spec.
Now, an AI prompt derived from the spec:
Write a Python function
longest_session(minutes_list)that returns the largest value inminutes_list. It must returnNonefor an empty list. Single-element list returns that element. The function must handle negative numbers and zero correctly.
The AI’s job here is constrained. There is no room to guess at the empty case, because the spec said it. There is no room to assume non-negative input, because the spec said negatives are handled. The function the AI produces is verifiable directly against the tests above. If it passes them, it is correct by your definition of correct, which is the only definition that matters.
Alternative with Raise Instead of Return None
The spec above chose to return None for the empty case. The same function could be specified differently, using the exception-raising approach from the chapter on Exceptions and Input Validation:
longest_session(minutes_list: list of int) -> int
Returns the largest value in `minutes_list`.
Edge cases: - Single-element list: returns that element. - All elements equal: returns that element. - List contains negative numbers: returns the largest (closest to zero).
Raises: - ValueError if minutes_list is empty.Two changes from the first version. The return type is now just int, not int or None, because the function no longer returns a sentinel value for the empty case. And there is a fifth section, Raises, which says exactly what exception the function raises and when.
Both versions are valid specs. The first asks the caller to remember to check for None. The second forces the caller to either ensure the list is not empty or catch the ValueError. The decision between them is a design choice, and the spec is where that choice is recorded — explicitly, not by accident.
Prompt shortest_session from a full spec
0 / 10 points
Write a prompt that asks a large language model to create Python code only.
The generated program must define a function named shortest_session(minutes_list) with this exact behavior:
- return the smallest value in
minutes_list, - return
Noneif the list is empty, - return the only value for a single-element list,
- handle all-equal lists,
- handle zero,
- and handle negative numbers by returning the smallest value, e.g.
-10from[-5, -10, -3].
The generated code will be tested by calling:
shortest_session([]) # should return Noneshortest_session([45]) # should return 45shortest_session([60, 60, 60]) # should return 60shortest_session([-5, -10, -3]) # should return -10shortest_session([0]) # should return 0Your prompt should explicitly include the exact function name, the empty-list behavior, the single-element case, all-equal lists, zero, and negative numbers.
Ask for Python code only, with no explanation.
Where You Are in the Nine-Skill Arc
Three skills are now in place:
| Part | AI skill | What it gives you |
|---|---|---|
| 1 | Reading code you didn’t write | Knowing what a piece of code does |
| 2 | Verifying behavior and finding edge cases | Knowing whether what it does is right |
| 3 | Specifying what you want — turning intent into prompts | Knowing what to ask for in the first place |
| 4 | Detecting plausible-but-wrong code | (next) |
| 5 | Comparing approaches — yours and the AI’s | (later) |
| 6 | Critiquing data designs | |
| 7 | Working with AI on libraries you’re still learning | |
| 8 | Critiquing AI-suggested visualizations | |
| 9 | Iterating with AI on a real project, and recognizing when to stop |
Reading + verifying + specifying = the three foundational skills. Reading tells you what something does. Verifying tells you whether what it does is right. Specifying tells you what to want in the first place. With all three, the AI relationship has shape: you decide what you want (specify), you ask for it precisely (prompt), and you check what you got (read, verify).
Summarize the AI skill from this part
0 / 10 points
Summarize the AI-collaboration skill from this part: specifying what you want.
Explain how it builds on reading code and verifying behavior from the earlier parts. In 3-6 sentences, use one example of a function specification in your answer.
A Note on Where the Specification Lives
In a small project, your specifications can live in comments above the function:
def longest_session(minutes_list): """Return the largest value in minutes_list, or None if empty.
Single-element list returns that element. Negative numbers are handled. """ ...This kind of comment, written with triple quotes immediately below a function definition, is called a docstring. Python attaches it to the function and makes it available through help(longest_session) and various editor features. The docstring serves as a permanent home for the function’s spec. You write it once when the function is created, and any reader — yourself, a colleague, an AI — can refer to it.
For larger projects, specs sometimes live in separate documents or design files. For this course, docstrings on the functions are enough. The point is that the spec is written down somewhere and visible to anyone reading the code.
Write a docstring specification
0 / 10 points
Define a pure function named average_temperature(readings).
Write a docstring immediately under the function definition that acts as a short specification. The docstring must mention:
- that the function returns the average of the readings,
- that an empty list returns
None, - that negative and zero readings are included in the average.
The function must:
- return
Nonefor an empty list, - return the arithmetic average otherwise,
- handle one value, zero, and negative values correctly,
- not ask for input,
- and not print.
Example calls and return values:
average_temperature([]) -> Noneaverage_temperature([10]) -> 10average_temperature([-5, 0, 10]) -> 1.6666666666666667The tests will load your program and inspect both the function behavior and the docstring.
Check Your Understanding
- What are the five parts of a function specification, and which one is optional?
- Why does writing tests from the spec rather than from the implementation matter?
- Look at the vague vs. specific prompts in this chapter. Pick one decision the vague prompt left to the AI, and explain how it could have gone wrong.
- Suppose you write a spec that just says “returns the average of the list”. What is missing from that spec, based on this chapter’s checklist?
AI Study Aid
Create a chapter diagram
We're looking into to what extent AI-generated diagrams could help with studying.
Use this study aid to generate an AI-generated visual summary of the material you just studied.
Each diagram style emphasizes the content in a different way, so you can choose the focus
that feels most useful.
Using the diagram generator is voluntary and does not affect your course progress. We encourage
you to try it out and see if it helps, but it's totally up to you! Your answers help us understand
how to make better study aids in the future.