Engineering Technical Prompts
Bad prompt:
Write a function to parse dates.
Good prompt:
Context:
- Language: Python 3.11
- Framework: none (stdlib only)
- Constraint: must run in <10ms per call, no external deps
Task:
Write a function `parse_date(s: str) -> date | None` that parses dates
in formats "YYYY-MM-DD", "DD/MM/YYYY", and "Month DD, YYYY".
Input: a string, possibly malformed or empty.
Output: a `datetime.date` object, or `None` if parsing fails (do not raise).
Requirements:
- Add type hints and a docstring with examples.
- Handle edge cases: empty string, whitespace-only, None input,
invalid day/month (e.g. 30/02/2024), ambiguous format conflicts.
- Add inline comments explaining format-detection logic.
- Include 5 unit tests covering normal cases and edge cases.
Progress:
- Step 1: Define context block (language/version, framework, runtime constraints)
- Step 2: Define input contract (types, nullability, valid ranges)
- Step 3: Define output contract (type, error-handling behavior)
- Step 4: List known edge cases explicitly
- Step 5: Require comments/docstrings and test coverage
- Step 6: Review draft prompt for ambiguity before sending
Step 1 — Context block. Always state: exact language version (e.g. "Python 3.11" not "Python"), framework + version, memory/time budget if relevant, and dependency constraints ("stdlib only", "no new packages").
Step 2 — Input/Output contract. Specify exact function/API signature if known. State types, nullability, valid ranges, and what happens on invalid input (raise? return None? default value?). Never leave this implicit.
Step 3 — Edge cases. Brainstorm and explicitly list: empty/null/zero inputs, boundary values, concurrency, malformed data, very large inputs, non-ASCII/unicode, duplicate entries, timeouts/network failures.
Step 4 — Documentation requirement. Require: docstrings with param/return descriptions, inline comments on non-obvious logic, and usage examples.
Step 5 — Verification requirement. Request unit tests or a self-check demonstrating edge cases pass.
Example 1: Input (vague request): "Write a caching decorator." Output (engineered prompt):
Context: Python 3.10, async codebase using asyncio, no third-party libs.
Constraint: cache must be bounded (max 1000 entries, LRU eviction), thread-safe
for asyncio single-loop use.
Task: Implement `@async_cache(maxsize=1000)` decorator for async functions.
Input: any async function with hashable positional/keyword args.
Output: wrapped async function returning cached result on repeat calls.
Edge cases to handle:
- Unhashable arguments -> bypass cache, call function directly, log a warning.
- Exceptions raised by wrapped function -> do not cache, propagate exception.
- Cache eviction when maxsize exceeded -> evict least-recently-used entry.
- Concurrent calls with same args before first completes -> share the same
in-flight future (avoid duplicate execution).
Require: docstring, inline comments on eviction logic, and 6 unit tests
(including concurrency and exception cases).
Example 2: Input (vague request): "Parse a CSV file." Output (engineered prompt):
Context: Node.js 20, TypeScript 5.3, using only built-in `fs` + `string` methods
(no csv-parse lib). Memory constraint: must stream, not load full file (files
up to 2GB).
Task: write `parseCsvStream(filePath: string): AsyncGenerator<Record<string,string>>`.
Input: path to a CSV file; first row is header.
Output: async generator yielding one object per row, keyed by header.
Edge cases:
- Trailing newline or missing final newline.
- Quoted fields containing commas or embedded newlines.
- Empty file -> generator yields nothing, no error.
- Rows with fewer/more columns than header -> throw a descriptive error
including the line number.
Require: TSDoc comments, explanation of the streaming/buffering approach,
and 5 test cases covering the edge cases above.
- State versions, not just names ("React 18.2" not "React").
- Make error-handling behavior explicit — never let the model guess whether to throw, return null, or log.
- Prefer enumerated edge cases over "handle edge cases properly" — the model fills gaps with assumptions otherwise.
- Ask for tests/examples as a forcing function for correctness.
- When performance matters, state the budget numerically (ms, MB, O(n) complexity) rather than "fast" or "efficient".
- Keep the prompt structured (Context / Task / Input / Output / Edge cases / Requirements) so nothing is skipped.
- Omitting language/framework version — leads to code using deprecated or mismatched APIs.
- Leaving error behavior implicit — causes inconsistent exception vs. null-return handling.
- Saying "handle edge cases" without listing any — model will handle only the obvious ones.
- Forgetting to ask for comments — resulting code is correct but unmaintainable.
- Mixing multiple unrelated tasks in one prompt — reduces precision of each requirement.