Skip to content

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:

Make this migration more robust.

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:

Normalize customer names and handle missing values gracefully.

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:

Still broken.

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:

Expected behavior
Edge cases
Performance requirements

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:

Add caching to the pricing service.

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:

Ask me 10 questions before fixing this typo.

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:

Should I give Claude all feedback in one message, or fix one issue at a time?

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.

These three issues interact. Consider them together before editing.

10.6 Pattern F: Sequential Independent Fixes

Use when issues are unrelated.

Fix issue 1, verify it, then move to issue 2.
Test yourself on this topic Interactive questions for Task 3.5 — Iterative Refinement, with instant explanations and scoring.
Start quiz →