The code-generation skeleton
Worked example: the dedupe function done right
Here is the same dedupe task from lesson 1, rewritten using the four-block skeleton:
Write a Python function
dedupe_preserve_order(items: list[str]) -> list[str].Constraints:
- O(n) time and O(n) extra memory.
- Preserve first-seen order.
- Case-sensitive ("Apple" and "apple" are distinct).
- No external imports.
- Type hints required.
Include 4
asserttest cases inside anif __name__ == "__main__":block:
- Empty list returns
[].- Single-item list returns the same list.
["a","b","a","c","b"]returns["a","b","c"].- Case-sensitivity:
["Apple","apple","Apple"]returns["Apple","apple"].Output ONLY a single Python code block, no prose around it.
Here is what Claude Sonnet 4.5 returned:
def dedupe_preserve_order(items: list[str]) -> list[str]:
seen: set[str] = set()
result: list[str] = []
for item in items:
if item not in seen:
seen.add(item)
result.append(item)
return result
if __name__ == "__main__":
assert dedupe_preserve_order([]) == []
assert dedupe_preserve_order(["a"]) == ["a"]
assert dedupe_preserve_order(["a", "b", "a", "c", "b"]) == ["a", "b", "c"]
assert dedupe_preserve_order(["Apple", "apple", "Apple"]) == ["Apple", "apple"]
Captured from Claude Sonnet 4.5 (claude-sonnet-4-5) on 2026-04-27. Re-runs may differ slightly.
The reasoning flow that produced this output:
Compare line-for-line to the lesson 1 output:
| Lesson 1 (vague prompt) | Lesson 5 (skeleton) |
|---|---|
| Two functions with the same name | One function, one signature |
| No type hints | Full type hints, including seen: set[str] |
| No tests | Four asserts, one per property |
| Markdown headings, prose, two implementations | Single code block, nothing around it |
O(n) and O(?) versions both shown | One O(n) implementation, locked by the constraint |
You can python dedupe.py this file directly. If the asserts pass, you can commit. That is the difference one well-shaped prompt makes — six minutes of careful prompt-writing instead of forty-five minutes of accept-edit-test-fix loops.
A note on what didn't happen: the model did not over-engineer. It did not add a Generic[T] type variable, did not write a docstring, did not handle None, did not add a CLI wrapper. Why? Because none of those were in INTENT or CONSTRAINTS. The skeleton's job is to give the model exactly enough room to do the work, and no more.
When you start using this skeleton on real code, you'll find the constraint list growing as you encounter team conventions: "match our existing style: 2-space indent, no trailing commas, prefer composition over inheritance." Save those as a snippet. The constraint list becomes a portable expression of how your team writes code.
Next module: applying the same discipline to debugging. :::
Sign in to rate