Iterating and Debugging with AI
The Vibe Coding Loop
Every productive vibe coding session follows the same rhythm: Prompt, Review, Test, Refine. This loop is the heartbeat of AI-assisted development. The faster and more precisely you can move through it, the more you can build.
Prompt: You describe what you want. This might be a new feature, a bug fix, a design change, or a refactoring task. The quality of your prompt determines the quality of the first output, as we covered in the prompting lesson.
Review: You look at what the AI generated. Not necessarily every line of code, but the overall structure, the approach it took, and whether it understood your intent. Did it create the right files? Does the component look roughly correct? Are there any obvious problems?
Test: You run the code and interact with it. Does the page load? Does the button do what it should? Does the form submit successfully? Do the error states work? Testing is where theory meets reality.
Refine: Based on what you saw in review and testing, you give the AI feedback. Maybe the feature works but the styling is off. Maybe the logic is correct but it throws an error on edge cases. Maybe it's perfect and you move on to the next task.
This loop repeats until the result meets your expectations. Most features go through 2-5 iterations. Simple changes might nail it on the first try. Complex features might take 10 or more rounds. Both are normal.
The key insight: each iteration should make measurable progress. If you're going in circles — the same error keeps coming back, or each fix introduces a new problem — that's a signal to change your approach, not to keep iterating.
Effective Feedback vs. Vague Feedback
The quality of your feedback directly determines how quickly AI can fix an issue. Vague feedback wastes both your time and the AI's processing power. Specific feedback leads to precise fixes.
Let's look at six side-by-side comparisons:
Pair 1: Something is broken
Vague: This doesn't work.
Effective: The submit button on the contact form at /contact returns a 404 error when clicked. Here's the console output: "POST /api/contact 404 (Not Found)". The form fields are filled in correctly and validation passes.
Pair 2: Styling issues
Vague: It looks weird on mobile.
Effective: On iPhone (375px width), the pricing cards overlap each other. The three-column grid isn't switching to a single column. The cards need to be full width and stacked vertically on screens below 768px.
Pair 3: Wrong behavior
Vague: The login isn't working.
Effective: After entering valid credentials and clicking "Sign In," the page redirects to /dashboard for a split second, then immediately redirects back to /login. This happens in a loop. I can see the session cookie is being set in DevTools, so the authentication seems to work but the middleware might be redirecting authenticated users incorrectly.
Pair 4: Performance
Vague: The page is slow.
Effective: The /dashboard page takes 8 seconds to load. The Network tab shows that the /api/analytics endpoint takes 6.5 seconds to respond. It's probably fetching too much data — we're loading all analytics for all time instead of just the current month.
Pair 5: Partial success
Vague: Almost, but not quite.
Effective: The dark mode toggle works — clicking the sun/moon icon switches the color scheme. But two things are wrong: (1) the preference doesn't persist — refreshing the page always starts in light mode, and (2) the chart component in the dashboard doesn't respect the dark mode variables, so it stays white even when everything else is dark.
Pair 6: New requirement
Vague: Can you also add validation?
Effective: Add client-side validation to the registration form: the email field should validate the format on blur (show "Please enter a valid email" below the field in red), the password field should require at least 8 characters with one number (show strength indicator: weak/medium/strong), and the "confirm password" field should show an error if it doesn't match. Don't submit the form if any validation fails.
The pattern is clear: effective feedback includes what you expected, what happened instead, and any error messages, URLs, or specific details that help the AI locate and understand the problem.
The Art of Specific Feedback
There's a simple structure you can follow every time you give feedback to AI:
1. What I expected to happen
2. What actually happened
3. Relevant evidence (error message, screenshot description, URL, behavior)
Here are some examples of this structure in action:
Expected: Clicking "Add to Cart" should show a toast notification
and update the cart count in the header.
Actual: The toast appears, but the cart count doesn't update until
I refresh the page.
Evidence: The addToCart server action returns successfully (I can see
the item in the database), so the issue is probably that the header
component isn't re-rendering after the action completes.
Expected: The search should filter blog posts as I type.
Actual: Nothing happens when I type. The post list stays the same.
Evidence: No console errors. I checked the React DevTools and the
search state is updating correctly, so the filtering logic might
not be connected to the post list rendering.
Expected: The image upload should accept JPEG and PNG files up to 5MB.
Actual: It accepts the file but the preview shows a broken image icon.
Evidence: The file uploads successfully to the /api/upload endpoint
(returns 200 with the URL), but the returned URL gives a 403 when
accessed directly. This might be a Supabase storage permission issue.
Notice how each of these gives the AI a clear starting point for investigation. You're not just saying "it's broken" — you're providing a diagnostic trail that leads to the solution.
Always Include the Error Message
This cannot be overstated: always paste the actual error message. Don't summarize it, don't paraphrase it, don't describe it from memory. Copy and paste the exact text.
Error messages contain precise information — file paths, line numbers, function names, and error codes — that the AI uses to pinpoint the problem. A summary like "there's a type error somewhere" loses all of this valuable information.
# Bad: summarizing the error
There's a type error in the user component.
# Good: pasting the actual error
Error in app/dashboard/page.tsx:
TypeError: Cannot read properties of undefined (reading 'name')
at UserGreeting (app/components/UserGreeting.tsx:14:22)
at Dashboard (app/dashboard/page.tsx:31:8)
The full error tells the AI exactly which file, which line, which property, and which component chain is involved. That's the difference between a targeted fix and a guessing game.
Collaborative Debugging Patterns
Over time, you'll develop a repertoire of debugging conversations with AI. Here are four patterns that cover most situations.
The Error Dump
The simplest and most common pattern. You encounter an error, you paste it, and you ask AI to diagnose it.
I'm getting this error when I try to build the project:
./app/dashboard/page.tsx
Type error: Property 'user' does not exist on type 'Session | null'.
29 | const session = await getServerSession(authOptions);
30 | const userName = session.user.name;
^
What's wrong and how do I fix it?
The AI will immediately recognize this as a null safety issue — session might be null, and you need to check for that before accessing .user.name. Paste the full error, and the fix is usually straightforward.
The Behavior Description
When there's no error but the behavior is wrong, describe what you see versus what you expect.
The navigation bar is supposed to be fixed at the top of the page so
it stays visible when scrolling. But it scrolls away with the rest of
the content. I added "fixed top-0" to the nav element. The nav is
inside the layout.tsx file, wrapped in a div with "flex flex-col min-h-screen".
No error message here, but the description is specific enough for AI to diagnose. The issue is likely that the parent flex container needs adjustment, or the main content needs a top margin to account for the fixed nav.
The Comparison
This pattern works great when something that used to work now doesn't.
The user avatar was displaying correctly in the header until I added
the notification bell feature in the last commit. Now the avatar
shows a broken image icon. I didn't change any avatar-related code,
so the notification bell changes must have broken something.
The last commit was: feat: add notification bell to header
Can you check what changed in that commit and figure out why the
avatar broke?
This gives AI a clear timeline: it worked before commit X, it's broken after commit X, so the problem was introduced by commit X. The AI can diff the commit and find the issue.
The Guided Investigation
Sometimes you have a hunch about where the problem is. Share it.
The checkout flow is failing on the payment step. I think the issue
is in app/api/checkout/route.ts — specifically in how we're creating
the Stripe Checkout Session. The error from Stripe says
"Invalid price ID" but the price ID in our database looks correct
(price_1234abc). Can you check if we're passing the price ID
correctly to the Stripe API?
Your hypothesis might be wrong, and that's fine. But giving AI a starting point for investigation is faster than asking it to search blindly.
Reading Stack Traces
Stack traces look intimidating if you've never seen one, but they follow a consistent format that's actually quite readable once you know what to look for.
Here's a typical stack trace:
TypeError: Cannot read properties of undefined (reading 'email')
at getUserProfile (C:\project\lib\users.ts:45:24)
at async Dashboard (C:\project\app\dashboard\page.tsx:18:20)
at async renderServerComponent (node_modules\next\...):
Let's break it down:
Line 1 — The error type and message: TypeError: Cannot read properties of undefined (reading 'email'). This tells you the category of error (TypeError) and what specifically went wrong (tried to read .email on something that's undefined).
Lines 2+ — The call stack: These show the chain of function calls that led to the error, starting from the most recent. The first line after the error message is usually where the problem is:
getUserProfileinlib/users.tsat line 45, column 24
The rest of the stack shows what called getUserProfile:
Dashboardinapp/dashboard/page.tsxat line 18
Lines referencing node_modules are framework internals — you usually don't need to worry about those.
Common Error Types
TypeError — You tried to use a value in a way that doesn't match its type. Most common: accessing a property on undefined or null. Fix: add null checks.
ReferenceError — You used a variable that doesn't exist. Usually a typo in a variable name or a missing import. Fix: check spelling and imports.
SyntaxError — The code has invalid syntax. A missing bracket, an extra comma, a misplaced semicolon. Fix: look at the line mentioned in the error.
404 (Not Found) — An HTTP request went to a URL that doesn't exist. Common cause: wrong API route path, missing page file, or typo in the URL.
500 (Internal Server Error) — The server crashed while processing a request. Check the server logs for the actual error. The 500 just means "something went wrong on the server side."
401 (Unauthorized) — The request doesn't have valid authentication. Missing or expired token, wrong API key, or the user isn't logged in.
403 (Forbidden) — The user is authenticated but doesn't have permission. Check role-based access control or resource ownership.
CORS Error — The browser blocked a request to a different domain. This is a server configuration issue, not a client-side bug. The API needs to send the right CORS headers.
The "Undo and Retry" Pattern
Sometimes iteration isn't converging. You've been going back and forth with AI for 15 minutes, and the code isn't getting closer to working — it's getting more complex with each iteration. This is the time to stop iterating and start fresh.
Here's the pattern:
# 1. Checkpoint your current state (in case you want to reference it later)
git add .
git commit -m "wip: save broken auth attempt for reference"
# 2. Revert to the last working version
git reset HEAD~1
git checkout -- .
# 3. Try a completely different approach
Now start a new conversation or a new prompt that takes a fundamentally different approach. Instead of tweaking the same broken solution, describe the problem fresh and let AI propose a new solution.
When This Is Necessary
Here are concrete signs that iteration has stalled:
- The same error keeps coming back. You fix it, something else breaks, you fix that, and the original error returns. The AI is in a loop.
- The code is getting more complex with each iteration. Each "fix" adds more workarounds, conditional logic, and special cases. The solution is growing when it should be getting simpler.
- You've been on the same issue for 15+ minutes. Set a mental timer. If a single issue takes more than 15 minutes of back-and-forth, the approach is probably wrong.
- The AI starts suggesting "workarounds." When AI says things like "as a workaround, you could..." it means the current approach has a fundamental limitation. A workaround on top of an AI suggestion is a house of cards.
- You're getting frustrated. This is a real signal. Frustration usually means the approach is wrong, not that you just need one more iteration.
The fresh start often solves the problem in a single prompt because the AI picks a different (and better) implementation strategy when starting from scratch.
Debugging Scenarios by Type
Different types of bugs require different debugging strategies. Here's a guide to the most common categories.
Build and Compilation Errors
These prevent your code from running at all. They're usually the easiest to fix because the error message tells you exactly what's wrong.
Module not found: Can't resolve '@/components/UserCard'
Diagnosis: The file doesn't exist at that path, or the import path is wrong. Check for typos in the path and make sure the file exists.
Type 'string' is not assignable to type 'number'.
Diagnosis: You're passing a string where a number is expected. Check the function signature and the data you're passing in.
Strategy: Paste the full error into your AI prompt. Build errors almost always have clear, actionable messages.
Styling Issues
CSS bugs are visual — you can see them but they don't produce error messages. This makes them harder to describe to AI.
Strategy: Be extremely specific about what you see versus what you expect. Mention screen sizes, specific elements, and exact behaviors.
The pricing cards are overlapping on iPad (768px width). The three
cards should be in a 2-column grid on tablet and 1-column on mobile.
Currently they're all in one row and the third card extends beyond
the right edge of the screen. The container has max-w-6xl and the
cards use w-1/3 without responsive variants.
Common styling culprits:
- Missing responsive variants (no
md:orlg:prefixes) - z-index conflicts (overlapping elements)
- Overflow hidden cutting off content
- Flexbox or grid not wrapping as expected
- Parent containers with fixed heights constraining children
Logic Bugs
The code runs without errors, but it produces the wrong result.
The discount calculation is showing 15% off instead of 20% off.
The subtotal is $100, the discount should be $20, but it shows $15.
The calculation is in lib/pricing.ts in the calculateDiscount function.
Strategy: Describe the expected output, the actual output, and the specific input values. If you can identify the function where the calculation happens, mention it.
API Errors
Network requests failing between your frontend and backend (or external services).
Strategy: Include the HTTP status code, the URL, the request method, and any error body. Check the Network tab in browser DevTools for this information.
POST request to /api/users returns 500.
Request body: { "name": "John", "email": "john@example.com" }
Response body: { "error": "relation \"users\" does not exist" }
This means the database table hasn't been created. Need to run migrations.
Common API error causes:
- 400: Request body missing required fields or has invalid data
- 401: Auth token missing or expired
- 403: User doesn't have permission for this action
- 404: Wrong URL path or the resource doesn't exist
- 500: Server-side crash — check server logs for the real error
- CORS: API doesn't allow requests from your frontend domain
Performance Issues
The app works but it's slow.
Strategy: Use browser DevTools to measure what's slow. The Performance tab, Network tab, and Lighthouse audit give you specific numbers to share with AI.
The /dashboard page takes 6 seconds to load. The Network tab shows
that the /api/analytics endpoint takes 5.2 seconds. It returns
45,000 rows of data. We should paginate this to return only the
current month's data (about 1,500 rows) and add server-side filtering.
TypeScript Errors
Type errors are compile-time warnings that prevent bugs but can be confusing if you're new to TypeScript.
Type '{ name: string; }' is not assignable to type 'User'.
Property 'email' is missing in type '{ name: string; }'
but required in type 'User'.
Strategy: Paste the full TypeScript error. These are highly structured and AI can almost always fix them instantly. The error is telling you exactly what property is missing and what type expects it.
When to Start Fresh vs. Keep Iterating
This decision determines whether you spend 5 minutes or 50 minutes on a problem. Here's a framework:
Keep Iterating When:
- Each iteration makes visible progress toward the solution
- The core approach is sound but details need adjustment
- You're fixing styling or layout tweaks
- The AI understands what you want but needs fine-tuning
- You're on iteration 2-4 and the trajectory is positive
Start Fresh When:
- The same error comes back after being "fixed" two or more times
- Each iteration makes the code more complex rather than simpler
- You've spent more than 15 minutes on a single issue
- The AI keeps suggesting workarounds instead of solutions
- You realize the initial approach was fundamentally wrong
- The codebase has accumulated so many patches that it's hard to follow
How to Start Fresh Effectively
Starting fresh doesn't mean losing everything. Here's the process:
- Commit the current broken state with a
wip:prefix so you can reference it - Revert to the last working version using
git reset - Identify what was wrong about the previous approach (this is the key step)
- Write a new prompt that explicitly avoids the problem. For example: "Last time, using client-side state for the cart caused sync issues. This time, use a server action approach with revalidation instead."
The explicit mention of what to avoid prevents AI from making the same architectural choice again.
Maintaining Momentum
A debugging session can drain your energy and derail your entire workflow if you let it. Here are strategies for staying productive.
Set a time limit. If a bug isn't resolved in 15 minutes, park it and move on. Work on something else — a different feature, a different page, a different part of the application. Often, the solution becomes obvious when you come back with fresh eyes.
Don't let one bug block everything. Unless the bug is truly blocking (the app won't build at all), there's usually other work you can do. Add a TODO comment, note the issue, and continue building other parts of the project.
Celebrate small wins. Each successful iteration is progress. Each bug you resolve is a skill you've gained. Vibe coding has a fast feedback loop — enjoy the speed of going from prompt to working feature.
Take breaks. This sounds simple but it's genuinely effective. If you've been staring at the same issue for 20 minutes, walk away for 5 minutes. Your subconscious keeps working on the problem even when your conscious mind stops.
Keep a running log. When you solve a tricky bug, write down what it was and how you fixed it. Over time, this becomes your personal debugging playbook. You'll start recognizing patterns: "Oh, this looks like that CORS issue I fixed last month."
What's Next
You can now iterate effectively, give precise feedback, diagnose errors by category, and know when to cut your losses and start fresh. These skills will serve you in every vibe coding session from now on.
But there's one more essential skill before we start building full applications: reading and reviewing AI-generated code. You don't need to be a programmer to review code — you just need to know what to look for. In the next lesson, we'll cover code review patterns that help you spot issues, understand structure, and know when to ask for a rewrite.