•
5 min read
The Discipline of Throwing Code Away
When Rebuilding AI-Generated Code Is Better Than Repairing a Bad Foundation

Hello Curious Coders,

Last time, we let an agent build an entire UI with almost no guidance. We wanted to see what would happen when the model had room to make every architectural decision for itself.

It got bad.

Now there is a pull request full of broken patterns, and the natural response is to open the files and start repairing them. That is what programmers do. We refactor awkward functions, move responsibilities, and keep working until the code takes the shape we wanted.

With AI-generated code, that instinct can lead us in the wrong direction. If the foundation is bad, every repair keeps the rejected design in front of the model. We spend more time teaching it how to continue inside a structure we already know we do not want.

Sometimes the disciplined move is to stop editing and throw the code away.

Repetition Is Instruction

Large language models respond to the patterns in their current context. When the surrounding code is clean, that works in our favor. Good examples show the model how this project names things, divides responsibilities, and represents state.

The same mechanism works against us when the foundation is wrong. If we keep asking the model to patch a badly organized LiveView, that organization remains the dominant example. Each repair requires the model to read and extend parts of the structure we rejected.

Bruce put it plainly:

“It might seem to make sense to fix the areas that are broken. But if we try to do so, we’re just going to reinforce these bad patterns. It’s better to just throw it away and improve our prompts for next time.”

– Bruce Tate

This does not mean deleting a branch whenever the model misses a detail. If the design is sound and a button needs a different label, keep going. A small correction refines a useful pattern.

The line appears when the repair becomes structural. If you need to replace the component boundaries, reorganize the state, or rewrite most of the template, the implementation is no longer helping. The rule is easy to remember: tweak a good design, discard a bad one.

🎯 Join Groxio's Newsletter

Weekly lessons on Elixir, system design, and AI-assisted development — plus stories from our training and mentoring sessions.

We respect your privacy. No spam, unsubscribe anytime.

What the Failed Branch Taught Us

The review showed exactly where the unguided build had gone off course.

The button component treated name and value as global HTML attributes instead of declaring them as part of the component’s interface. The game views had long stretches of markup with old EEx patterns where Phoenix directives and smaller components should have expressed the intent. A dictionary containing thousands of words lived in a hard-coded tuple rather than curated data files. The games also sat at the top level of the application, with no component boundary separating them.

The branch was not worthless. Its new and render functions followed the CRC-style organization we wanted, and using the socket as the LiveView state type gave the server-side code a sensible shape.

Those decisions belong in the next prompt, not necessarily in the next branch.

That distinction makes deletion useful. We are not forgetting the attempt. We are converting an expensive implementation into a more precise specification.

Before You Patch, Run This Test

Take one structural mistake from the branch. The original button declaration looked like this:

attr :rest, :global, include: ~w(name value)
slot :inner_block, required: true

def button(assigns) do
  ~H"""
  <button {@rest}><%= render_slot(@inner_block) %></button>
  """
end

This code may work, but it hides two meaningful inputs inside the catch-all attribute bag. A rebuilt component makes its interface explicit:

attr :name, :string
attr :value, :string
attr :rest, :global
slot :inner_block, required: true

def button(assigns) do
  ~H"""
  <button name={@name} value={@value} {@rest}>
    <%= render_slot(@inner_block) %>
  </button>
  """
end

Changing those declarations in isolation would be a reasonable edit if the rest of the component system were sound. In this branch, the mistake was one sign of a larger problem: the model had not been given a clear component architecture.

Before patching generated code, ask one question:

“Am I improving a sound design, or teaching the model how to survive inside a bad one?”

If it is a small enhancement, edit. If the answer requires a major structural repair, inspect the failure and write down what the next attempt must do. Then return to the clean starting point.

For this branch, that means loading separate answer and guess files, building the HTML from components with intentional names, and using current Phoenix directives. It also means giving each game a clear stateful component design without introducing the separate state and complexity of classic live components.

The Next Attempt Starts With the Prompt

Throwing code away stops feeling wasteful once the artifact you value is the lesson, not the branch. You stop measuring progress by how much generated code survived and start measuring it by how much clearer the next instruction became.

The failed build gave us something better than salvageable code: it exposed the decisions the original prompt left open.

If you want to practice this review-and-rebuild workflow in a Phoenix project, join us in the Groxio AI course. Next, we will use what the branch taught us to improve the prompt instead of the code.


🤖 Throw Away Bad Patterns Before They Compound

This comes from Bruce's AI Agents course — the anti-vibe-coding curriculum. Learn structured oversight that turns failed branches into clearer instructions, so you can rebuild without losing architecture decisions or letting AI become a crutch. Available via monthly subscription — try it for one month.

— Paulo & Bruce

Bruce Tate's avatar
Bruce Tate
System architecture expert and author of 10+ Elixir books.
Paulo Valim's avatar
Paulo Valim
Full‑stack Elixir developer and educator teaching modern Elixir and AI‑assisted development.