0Pricing
AI Prompt Engineering · Lesson

Systematic Debugging Approach

Binary search on prompt sections: remove half, test, narrow down the issue.

Systematic Debugging Approach is a free AI Prompt Engineering lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Prompt Engineering learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Debugging Mindset

Prompt debugging mirrors software debugging: do not change multiple things at once, do not guess randomly, and do not deploy a fix you cannot explain. A systematic approach uses binary search logic — narrow the problem space by half with each test — to find the minimal failing case efficiently.

Step 1: Reproduce the Failure

Before debugging, reliably reproduce the failure. A failure you cannot reproduce consistently cannot be debugged systematically.

Run the prompt 5 times on the same input. If it fails every time: deterministic failure — easy to debug. If it fails sometimes: probabilistic failure — set temperature=0 first to eliminate randomness, then re-test.

import openai
client = openai.OpenAI(api_key='sk-...')

def run_prompt(prompt, user_input, temperature=0):
    resp = client.chat.completions.create(
        model='gpt-4o',
        messages=[
            {'role': 'system', 'content': prompt},
            {'role': 'user', 'content': user_input}
        ],
        temperature=temperature
    )
    return resp.choices[0].message.content

# Reproduce with temperature=0 to eliminate randomness
for i in range(5):
    output = run_prompt(failing_prompt, test_input, temperature=0)
    print(f'Run {i+1}:', output[:100])

Step 2: Create a Minimal Reproducible Prompt

A minimal reproducible prompt (MRP) is the shortest prompt that still triggers the failure. Removing irrelevant parts isolates the problematic section and makes the failure undeniable.

Start with your full prompt and remove half the content. Test. If still failing: the problematic section is in the half you kept. Repeat. This is binary search on the prompt.

def binary_search_prompt(prompt_lines, user_input, fail_fn):
    '''Binary search: find the minimal set of lines that causes the failure.'''
    if len(prompt_lines) == 1:
        return prompt_lines  # Minimal failing unit found

    mid = len(prompt_lines) // 2
    first_half = prompt_lines[:mid]
    second_half = prompt_lines[mid:]

    # Test first half
    if fail_fn('\n'.join(first_half), user_input):
        return binary_search_prompt(first_half, user_input, fail_fn)
    # Test second half
    elif fail_fn('\n'.join(second_half), user_input):
        return binary_search_prompt(second_half, user_input, fail_fn)
    else:
        # Both halves pass — interaction effect between halves
        return prompt_lines

The Removal Test

A simpler form of binary search: systematically remove individual sections and see if removal fixes the problem. This works when the prompt has clearly delimited sections (system instructions, context, examples, format spec).

sections = {
    'role_instruction': 'You are a precise JSON API. Respond only with valid JSON.',
    'context': 'The user is asking about our product catalog.',
    'format_spec': 'Return a JSON object with keys: name, price, available.',
    'examples': 'Example: {"name": "Widget", "price": 9.99, "available": true}',
    'safety': 'Do not reveal internal pricing strategy.'
}

def test_without(section_to_remove, user_input):
    reduced = {k: v for k, v in sections.items() if k != section_to_remove}
    prompt = '\n'.join(reduced.values())
    output = run_prompt(prompt, user_input)
    print(f'Without {section_to_remove}: {evaluate(output)}')

for section in sections:
    test_without(section, 'What is the price of a Widget?')

A/B Testing Prompt Sections

A/B testing for prompts means creating two versions of a single section and comparing their outputs on the same inputs. Unlike removal tests, A/B tests evaluate alternative wordings rather than presence/absence.

# A/B test: vague vs precise format instruction
variant_A = 'Return a JSON object.'
variant_B = 'Return a valid JSON object. No markdown, no code fences, no prose. Only the raw JSON.'

test_inputs = [
    'What is the price of Widget A?',
    'List all available products.',
    'Is Widget B in stock?'
]

def run_ab_test(base_prompt, variant, inputs, n_runs=5):
    pass_count = 0
    for inp in inputs:
        for _ in range(n_runs):
            prompt = base_prompt.replace('{{FORMAT}}', variant)
            output = run_prompt(prompt, inp)
            if is_valid_json(output):
                pass_count += 1
    return pass_count / (len(inputs) * n_runs)

print('A pass rate:', run_ab_test(template, variant_A, test_inputs))
print('B pass rate:', run_ab_test(template, variant_B, test_inputs))

Differential Testing

Differential testing compares two nearly identical prompts to find what change caused the regression. This is useful when 'it worked last week' but does not work now.

Diff the old prompt against the new prompt, identify changed sections, then test each changed section in isolation.

import difflib

def show_prompt_diff(prompt_v1, prompt_v2):
    diff = difflib.unified_diff(
        prompt_v1.splitlines(),
        prompt_v2.splitlines(),
        fromfile='v1',
        tofile='v2',
        lineterm=''
    )
    for line in diff:
        print(line)

show_prompt_diff(working_prompt, failing_prompt)
# Output shows exactly which lines changed between versions
# Test reverting each changed section individually

Testing Inputs vs Testing Prompts

Two dimensions to test: the prompt and the input. A prompt may work on simple inputs but fail on complex ones. A useful debugging move: if the prompt fails on a complex input, try a simpler version of the input to confirm the prompt itself is sound.

# Input complexity ladder
inputs_by_complexity = [
    'What is 2 + 2?',             # trivially simple
    'Summarize this sentence.',    # simple task
    'Analyze this 500-word essay.',  # moderate
    'Compare 10 documents and extract contradictions.'  # complex
]

# Find the complexity level where the prompt starts failing
for inp in inputs_by_complexity:
    output = run_prompt(failing_prompt, inp)
    result = 'PASS' if evaluate(output) else 'FAIL'
    print(f'{result}: {inp[:60]}')
# First FAIL indicates where the prompt breaks down

The Minimal Reproducible Prompt Pattern

The MRP for a prompt debugging session follows this structure:

  1. One-sentence role (if needed)
  2. One-sentence task instruction
  3. Format instruction
  4. The minimal input that reproduces the failure

If this 4-line prompt still fails, the problem is in the model or format. Add complexity back one section at a time until the failure reappears — that section is the culprit.

# Start minimal
MINIMAL_PROMPT = (
    'You are a data extractor.\n'
    'Extract the product name and price from the text.\n'
    'Respond with JSON: {"name": "...", "price": ...}\n'
)

minimal_input = 'Widget Pro costs $49.'

# Test: if this works, the problem is in something added on top
output = run_prompt(MINIMAL_PROMPT, minimal_input)
print(output)
# Expected: {"name": "Widget Pro", "price": 49.0}

Tracking the Debugging Session

Document each test during a debugging session. Without notes, you may repeat the same tests or forget which hypotheses were eliminated.

debug_log = [
    {
        'test': 'base_prompt_v5',
        'hypothesis': 'failing due to format conflict',
        'result': 'FAIL',
        'notes': 'JSON prefix still present'
    },
    {
        'test': 'base_prompt_v5_no_markdown_hint',
        'hypothesis': 'removing markdown hint from user message fixes conflict',
        'result': 'PASS',
        'notes': 'Output is clean JSON. Root cause confirmed: format conflict.'
    }
]

import json
with open('debug_session.json', 'w') as f:
    json.dump(debug_log, f, indent=2)

When to Stop Debugging and Change Strategy

Sometimes debugging a prompt reaches diminishing returns. Signs it is time to change strategy:

  • You have spent more than 2 hours narrowing to the same failure
  • The minimal prompt still fails with a clear, simple instruction
  • A/B tests show no statistically significant difference

Alternatives: switch to function calling (structured outputs), add a post-processing validation step, decompose the task into two simpler prompts, or upgrade the model.

Fixing vs Hardening

After finding and fixing the root cause, harden the prompt to prevent similar failures:

  • Add the failing test case to your test suite as a regression test
  • Add a defensive instruction: 'Even if the input is unusual, always return JSON'
  • Add output validation so the failure is caught programmatically, not in production

A fixed prompt that is not hardened will fail again on the next edge case.

def safe_run_prompt(prompt, user_input):
    output = run_prompt(prompt, user_input)
    try:
        parsed = json.loads(output)
        return parsed
    except json.JSONDecodeError:
        # Fallback: ask the model to fix its own output
        fix_prompt = f'The following is not valid JSON. Rewrite it as valid JSON only:\n{output}'
        fixed = run_prompt('', fix_prompt)
        return json.loads(fixed)

Knowledge Check

In prompt binary search debugging, after removing the first half of the prompt and the failure disappears, what does this tell you?

Recap: Systematic Debugging

A systematic approach to prompt debugging:

  • Reproduce: set temperature=0, run 5 times, confirm the failure is consistent
  • Minimize: binary search on prompt sections to find the minimal failing prompt
  • A/B test: compare alternative wordings of the failing section
  • Differential: diff working vs failing prompt versions to find regression
  • Document: log each test, hypothesis, and result
  • Harden: add the fixed case to your test suite

Next lesson: logging and documentation strategies for long-term prompt maintenance.

Frequently asked questions

Is the “Systematic Debugging Approach” lesson free?

Yes — the full text of “Systematic Debugging Approach” is free to read here on the web, and the AI Prompt Engineering course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Prompt Engineering course, upgrade to CoddyKit PRO.

What will I learn in “Systematic Debugging Approach”?

Binary search on prompt sections: remove half, test, narrow down the issue. You practise AI Prompt Engineering with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start AI Prompt Engineering?

No prior experience is required. AI Prompt Engineering on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Systematic Debugging Approach” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this AI Prompt Engineering lesson?

Yes. Every AI Prompt Engineering lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Diagnosing Unexpected Outputs
  2. Root Cause Analysis for Prompts
  3. Systematic Debugging Approach
  4. Logging and Documentation Strategies
← Back to AI Prompt Engineering