Apply Iterative Refinement Techniques for Progressive Improvement
This module is about getting better results from Claude Code by giving it concrete feedback loops, not vague corrections.
How to improve Claude’s output progressively using:
- Concrete input/output examples
- Tests and failure output
- Interview-first clarification
- Specific edge cases
- Batching interacting issues
- Sequentially fixing independent issues
Claude Code’s own docs describe its working loop as: gather context, take action, verify results, and repeat based on what it learns from tool outputs.
For coding tasks, Claude may run tests, read failures, edit files, and rerun tests until the task is complete. (Claude)
1. Core Mental Model
This is about replacing vague guidance with observable signals.
Weak:
Better:
Here are three failing cases.
Update the migration so these inputs produce the expected outputs, then run the test suite and iterate until it passes.
Shortcut:
- Ambiguous transformation → give examples.
- Unreliable implementation → give tests.
- Unfamiliar domain → use interview pattern.
- Edge-case bug → give exact input + expected output.
- Interacting issues → provide together.
- Independent issues → fix sequentially.
Claude Code best practices explicitly recommend giving Claude a check it can run, such as tests, a build, a linter, fixture comparison, or screenshot comparison, because that lets Claude read the result and iterate until the check passes. (Claude)
2. Concrete Input/Output Examples
The official guide says concrete input/output examples are the most effective way to communicate expected transformations when prose is interpreted inconsistently.
This is especially important for:
Data migrations
Formatting transformations
Code generation patterns
API response shaping
Schema conversions
Validation behavior
Anthropic’s prompting docs also say examples are one of the most reliable ways to steer output format, tone, and structure, and recommend making examples relevant, diverse, and structured. (Claude)
2.1 Weak Instruction
Bad example:
Problems:
- What does “normalize” mean?
- Should null become empty string, unknown, or remain null?
- Should whitespace be trimmed?
- Should casing change?
- What about non-Latin names?
2.2 Better: 2–3 Concrete Examples
Update normalizeCustomerName(input) so it follows these examples:
Example 1:
Input:
{ "firstName": " John ", "lastName": " Doe " }
Expected output:
"John Doe"
Example 2:
Input:
{ "firstName": "John", "lastName": null }
Expected output:
"John"
Example 3:
Input:
{ "firstName": null, "lastName": null }
Expected output:
null
- Preserve non-empty name parts, trim whitespace, title-case ASCII names, and return null only when both name parts are missing.
This gives Claude enough information to infer the transformation.
3. Example-Driven Correction
Concrete examples are also the best way to correct a wrong implementation.
Suppose Claude wrote a migration script that converts legacy records and make some mistakes:
Better correction:
Fix null handling in the migration. These cases must pass:
Input:
{ "legacyStatus": null }
Expected:
{ "status": "UNKNOWN" }
Input:
{ "legacyStatus": "enabled" }
Expected:
{ "status": "ACTIVE" }
Input:
{ "legacyStatus": "disabled" }
Expected:
{ "status": "INACTIVE" }
Do not default null to ACTIVE.
This is exactly the kind of edge-case corrections needed in the examples.
4. Test-Driven Iteration
The official statement emphasizes writing tests first, then iterating by sharing test failures.
This pattern is:
1. Define expected behavior.
2. Write tests for normal cases, edge cases, and performance requirements.
3. Ask Claude to implement.
4. Run tests.
5. Share failing output.
6. Claude fixes based on concrete failure signal.
7. Repeat until tests pass.
Claude Code docs recommend giving Claude verification criteria and show the same general pattern: provide a check, have Claude run it, read the result, and iterate. (Claude)
4.1 Better Test-Driven Prompt
First write tests for validateEmail covering:
Valid:
- user@example.com
- first.last+tag@example.co.uk
Invalid:
- invalid
- user@.com
- @example.com
- user@example
- null
- empty string
Then implement validateEmail so the tests pass.
Run the targeted test file and iterate until it passes.
This aligns with Claude Code’s recommended practice of providing test cases and asking Claude to run verification after implementation. (Claude)
5. Sharing Test Failures Effectively
When tests fail, do not just say:
Give Claude the failure signal.
The following test is failing:
Test:
validateEmail(null) should return false
Failure:
TypeError: Cannot read properties of null (reading 'includes')
at validateEmail src/validation/email.ts:14
Expected:
false
Actual:
exception thrown
Fix the null handling without breaking the passing cases.
Why this works:
It identifies the failing input.
It gives expected vs actual behavior.
It includes the stack trace.
It constrains the fix not to regress passing cases.
Claude Code’s agent loop documentation gives a similar example where Claude runs a test suite, reads failures, reads relevant files, edits code, reruns tests, and stops when tests pass. (Claude)
6. What Tests Should Cover
Tests are not just “unit tests exist.” The docs explicitly mentions:
6.1 Test Suite Design Checklist
1. Happy path
2. Common invalid inputs
3. Null / undefined / empty values
4. Boundary values
5. Existing behavior that must not regress
6. Error handling
7. Performance constraints if relevant
8. Idempotency if relevant
9. Backward compatibility if relevant
7. The Interview Pattern
The interview pattern means asking Claude to ask you questions before implementation.
Use it when:
- The domain is unfamiliar.
- There are hidden tradeoffs.
- Multiple design choices exist.
- Requirements are incomplete.
- Operational failure modes matter.
- You suspect you have not considered important cases.
Claude Code’s Agent SDK docs describe an AskUserQuestion mechanism for clarifying questions. They give examples such as Claude asking about tech stack choices before proceeding when multiple valid directions exist. (Claude)
7.1 Interview Pattern Prompt
Before implementing, interview me.
Ask questions that would affect the design. Focus on:
- cache invalidation
- failure modes
- data consistency
- rollback strategy
- performance constraints
- security implications
- backward compatibility
After I answer, summarize the design constraints and propose an implementation plan.
Do not edit files until then.
7.2 Good Use Case: Cache Design
Weak:
Better:
Before implementing caching for the pricing service, interview me about:
- acceptable staleness
- invalidation triggers
- cache key structure
- per-tenant isolation
- failure behavior when cache is unavailable
- metrics and observability
7.3 When Not to Use the Interview Pattern
Do not overuse interviews for trivial changes.
Bad:
Use the interview pattern when developer input can materially change the design.
8. All Issues at Once Vs Sequential Refinement
This is one of the trickiest concepts.
The question is:
8.1 Provide All Issues in One Message When They Interact
Use a single detailed message when fixes affect each other.
Examples:
- API schema change + validation behavior + error response format
- Migration null handling + idempotency + performance batching
- Cache key design + invalidation + tenant isolation
- UI state model + loading behavior + error rendering
- Auth middleware order + permission checks + audit logging
Why?
- Fixing one in isolation may create rework.
- The correct design depends on seeing all constraints together.
- Claude needs the full constraint set before editing.
This is better than giving one issue, waiting for Claude to implement, then revealing the next issue that invalidates the prior design.
8.2 Fix Sequentially When Issues Are Independent
Use sequential iteration when problems do not affect one another.
Examples:
Typo in error message
Missing import
One failing unit test
Formatting issue
One accessibility label
One isolated validation condition
Why?
Sequential fixes keep each change small.
It is easier to verify each step.
There is less chance of over-editing.
10. Iterative Refinement Patterns
10.1 Pattern A: Examples First
Use when Claude’s transformation is inconsistent.
Here are 3 examples of the exact transformation I expect.
Update the implementation to match them.
Add these examples as tests.
Run the tests and iterate.
10.2 Pattern B: Tests First
Use when behavior must be verified.
Write tests covering the expected behavior and edge cases before implementation.
Then implement.
Then run the tests and fix failures.
10.3 Pattern C: Failure-Driven Correction
Use when tests already fail.
Here is the failing test output.
Expected: ...
Actual: ...
Stack trace: ...
Fix only the root cause and rerun the targeted test.
10.4 Pattern D: Interview First
Use when requirements are incomplete or domain assumptions matter.
Before implementing, ask me the design questions that would affect the solution.
After I answer, summarize constraints and propose the implementation.
10.5 Pattern E: Batch Interacting Issues
Use when constraints affect one another.
10.6 Pattern F: Sequential Independent Fixes
Use when issues are unrelated.