Blog/Why Beginners Should Read Broken Code Earlier

Why Beginners Should Read Broken Code Earlier

Beginners are asked to write new code long before they are asked to read broken code. That order is backwards more often than we admit.

Most programming work is not a heroic blank-file performance. It is reading a thing that almost works, figuring out what the author thought was happening, and then finding the one small place where reality disagreed.

Beginners need that skill early.

Not after they are "done with the basics." Not after they have built five polished projects. Early. While the basics are still forming.

Because debugging is not a separate advanced topic. Debugging is how the basics become real.

Fresh Code Hides Too Much

Starting from a blank file feels pure. It is also a lot to ask.

The learner has to understand the requirement, remember syntax, choose an approach, name variables, manage control flow, and notice mistakes. If the solution fails, the failure could be anywhere.

Broken code narrows the task.

Instead of "Make this whole thing," the task becomes:

This code is supposed to do one specific thing.
It does not.
Find out why.

That is a powerful learning frame.

The learner has to read carefully. They have to compare expected behavior to actual behavior. They have to form a hypothesis. They have to test a small change.

That is programming.

A Small Bug Teaches Several Skills

Look at this function:

function getPassingScores(scores) {
  const passing = [];

  for (const score of scores) {
    if (score > 70) {
      passing.push(scores);
    }
  }

  return passing;
}

console.log(getPassingScores([68, 72, 91]));

The learner expects:

[72, 91]

The function returns something stranger:

[
  [68, 72, 91],
  [68, 72, 91],
]

The fix is tiny:

function getPassingScores(scores) {
  const passing = [];

  for (const score of scores) {
    if (score > 70) {
      passing.push(score);
    }
  }

  return passing;
}

But the learning is not tiny.

The bug tests variable naming, loop variables, array mutation, conditionals, and the difference between the whole collection and the current item. That is a lot of useful pressure in a small space.

Writing this function from scratch is useful too. But reading the broken version forces a different question:

What did this code actually do?

That question builds sharper programmers.

Reading Code Builds Prediction

Good debugging starts before running the code.

The learner should be able to point at a line and say, "After this line, I expect this value to be..."

That sounds basic. It is not. It is the heart of programming.

Try this:

function countCompleted(tasks) {
  let count = 0;

  for (const task of tasks) {
    if (task.done = true) {
      count++;
    }
  }

  return count;
}

The bug is the assignment inside the if condition. But the deeper lesson is about prediction.

A learner who reads carefully can ask:

  1. What is task.done before the if line?
  2. What does task.done = true do?
  3. What value does an assignment expression produce?
  4. Why does the count increase for every task?

That is much better than "Oops, use three equals signs."

The surface fix is:

if (task.done === true) {
  count++;
}

The lasting lesson is that code has values at every step. Debugging is the practice of tracking them honestly.

Broken Code Reduces Shame

There is another reason to give beginners broken code: it normalizes bugs.

When every exercise starts from a clean prompt, beginners can start believing that mistakes are personal evidence. The blank file becomes a little trial. If they do not produce the right answer, they failed.

Broken-code exercises change the mood.

The bug is already there. The learner did not create it. Their job is to investigate.

That small emotional shift matters. It lets them be curious instead of defensive.

Professional developers spend a ridiculous amount of time with code that is wrong, unclear, stale, partial, or surprising. Beginners should not be protected from that reality for too long. They should meet it in small, survivable doses.

Not giant mystery bugs. Not framework stack traces with 40 irrelevant lines. Small broken examples with a clear expected behavior.

That is how debugging becomes a habit instead of a panic response.

Repair Exercises Beat Trivia

Too much beginner practice asks learners to identify facts:

What does filter return?
What is the difference between let and const?
What does DOM stand for?

Some facts matter. But trivia is cheap.

A repair task asks for applied understanding:

This function should return the names of active users.
It currently returns undefined.
Fix it without changing the function name or input data.

Now the learner has to inspect the code:

function getActiveNames(users) {
  users
    .filter((user) => user.active)
    .map((user) => user.name);
}

The issue is not filter() or map(). Both methods are fine. The function never returns the result.

function getActiveNames(users) {
  return users
    .filter((user) => user.active)
    .map((user) => user.name);
}

That tiny repair teaches function return values, method chaining, and the difference between calculating something and handing it back.

No flashcard can do that as well.

Reading Broken Code Prepares Learners For Projects

Projects are full of broken code because projects are full of code.

That sounds obvious, but it has consequences. A learner who only practices fresh solutions may feel completely lost when a project reaches the messy middle. The app sort of works. One feature regressed. The old data shape no longer matches the new UI. A click handler fires, but with the wrong value.

That is when reading matters.

The learner needs to trace what already exists. They need to see the difference between a bad assumption and a typo. They need to change one thing without breaking three nearby things.

Repair exercises are a bridge into that world.

They teach learners to approach code with questions:

  1. What is this supposed to do?
  2. What does it actually do?
  3. Where do those two stories first split?
  4. What is the smallest change that proves my hypothesis?

That mindset is useful in every project, every codebase, and every bug report.

The Right Kind Of Broken

Broken-code practice has to be designed carefully.

Good beginner bugs are small, local, and tied to a concept the learner is actively studying. A missing return, a mistaken assignment, an off-by-one loop, a wrong variable, a callback that does not return, a selector that returns null.

Bad beginner bugs are vague and theatrical. They require unrelated setup, hidden framework knowledge, or guessing what the exercise author meant. That kind of practice does not build debugging skill. It builds resentment with syntax highlighting.

The best repair tasks are specific:

Expected: ["Ada", "Lin"]
Actual: undefined
Constraint: Do not change the input array.

Now the learner has a target, a signal, and a boundary.

That is enough structure to make the problem fair, and enough friction to make it worth solving.

Key Takeaways

  • Beginners should read and repair code earlier, not only after they can write everything from scratch.
  • Broken-code exercises teach prediction, value tracking, and careful reading.
  • Repair tasks reduce shame because the bug is the object of study, not proof that the learner is bad.
  • Debugging practice prepares learners for the messy middle of real projects.
  • The best beginner bugs are small, local, and tied to the concept being learned.
Share this post: