Skip to content
Ameen Abozaid
PublishedBEG-057-L01

Generated code is a draft, not an answer

  • Lesson 1 of 5
  • 12 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

An AI coding assistant can produce a working-looking method in seconds. That speed is useful, and it is also the trap: code that looks finished is easy to accept without checking it.

In this lesson you will learn to treat generated code as a draft written by a fast but sometimes careless assistant, and yourself as the proofreader. You will meet the four kinds of faults that show up again and again (off-by-one errors, missing edge cases, wrong complexity, and invented library calls), see each one in real code that we compiled and ran, and learn a six-step workflow for using a draft safely.

Why this matters: in BEG-056 you learned how an assistant works and why fluent text is not the same as correct text. This module turns that idea into a habit you can practice. Reviewing code you did not write is also part of every pull request in a real team, and the Junior level builds on it. You need the testing skills from BEG-045 (JUnit tests, boundaries, invalid input) and the hand-tracing skills from BEG-016.

The next lesson, "Reading and tracing code you did not write", teaches the first step of the workflow in detail.

Learning Objectives

By the end of this lesson you will be able to:

  • Explain why generated code that compiles and looks tidy can still be wrong.
  • Name the four common faults in generated code and recognize an example of each.
  • Say who is responsible for generated code that you submit or ship.
  • Apply the rule "if you cannot explain a line, you cannot use it".
  • Follow the workflow: requirement, draft, read, test, fix, explain.

Written Explanation

What the assistant is doing, and what it is not

An assistant writes code the way it writes any text: it predicts what is likely to come next, based on patterns in the examples it learned from (you saw this in BEG-056). It does not run your program, and it does not know your requirement unless you wrote it down, and even then it only has your words. So its output has two properties at once:

  • It is usually plausible: the style is right, the names are sensible, the structure looks familiar.
  • It is sometimes wrong: in a way that the plausible style hides.

GitHub's own documentation for its coding assistant says the same thing: suggestions may look valid while being wrong or not matching what you meant, and you should review and test each one before accepting it.

Four faults to look for

FaultWhat it looks likeWho catches it
Off-by-one errorA loop starts at 1 instead of 0, stops one item early, or uses <= where < is neededA trace or a boundary test
Missing edge caseFails for an empty array, null, a negative number, or all-equal valuesA test for that input
Wrong complexityCorrect answers, but far too slow on large input, for example contains inside a loopA size test, or reading the loops
Invented library callUses a method that does not exist, or exists in another language or libraryThe compiler (the friendly case)

Notice that only the last fault is caught for free. The compiler rejects a method that does not exist. It says nothing about the other three: a program with an off-by-one error compiles perfectly and simply gives wrong answers.

There is a subtler version of the invented call: a method that exists, but in a different library or language, or in a newer Java version than you use. firstOrNull() is a real method in Kotlin. List.getFirst() is real in Java 21 (added to List in that release) and does not exist in Java 17. When the compiler rejects a call, check the documentation for your Java version before you decide how to fix it.

You are responsible

When you submit an assignment, open a pull request, or ship a feature, you own the code. "The assistant wrote it" is not a defence: not for a bug in production, and not in a course that asks you to show your own understanding. The tool has no accountability. You do.

This leads to one rule that you will use for the rest of your career:

If you cannot explain a line, you cannot use it.

"Explain" means more than repeating the line in words. It means you can say what it does, why it is needed, and what would break if it were removed.

The workflow: requirement, draft, read, test, fix, explain

  1. Requirement. Write down, in plain words, exactly what the method must do, including what happens for empty, null, and invalid input. If you cannot write this, you are not ready to judge any code.
  2. Draft. Ask the assistant. Give it the requirement (BEG-056 L03 covered how to ask).
  3. Read. Read the draft once for the idea, then trace it by hand (the next lesson).
  4. Test. Run tests that you wrote from the requirement (lesson 3).
  5. Fix. When a test fails, decide whether the code or your test is wrong by rereading the requirement, then fix and rerun.
  6. Explain. Write a short comment or README note in your own words, and disclose the AI use if your course or employer requires it (lesson 4).

The order matters. If you write your tests after looking at the draft, they tend to agree with it, and the draft's mistakes become your tests' expectations.

Examples

Example 1: invented calls (simple)

An assistant is asked for "the first name in a list, or null when the list is empty" and for "is this string null or empty?". It produces:

java
static String firstName(List<String> names) {
    return names.firstOrNull();
}

static boolean blank(String s) {
    return String.isNullOrEmpty(s);
}

Compiling this with JDK 21 gives:

text
cannot find symbol
  symbol:   method firstOrNull()
  location: variable names of type java.util.List<java.lang.String>
cannot find symbol
  symbol:   method isNullOrEmpty(java.lang.String)
  location: class java.lang.String

Both calls look natural, and both come from other places: firstOrNull from Kotlin, isNullOrEmpty from libraries such as Guava. The Java documentation lists String.isEmpty() and isBlank() (the latter since Java 11), but no static isNullOrEmpty. Real fixes, using only what you already know:

java
static String firstName(List<String> names) {
    return names.isEmpty() ? null : names.get(0);
}

static boolean blank(String s) {
    return s == null || s.isEmpty();
}

The lesson: a compile error on a call is a gift. Look up the real method, and do not ask the assistant to "fix it" without reading the documentation.

Example 2: an off-by-one error (practical)

Requirement: sum(int[] a) returns the sum of all items; for an empty array it returns 0. The draft:

java
static int sum(int[] a) {
    int s = 0;
    for (int i = 0; i < a.length - 1; i++) s += a[i];
    return s;
}

It compiles. Running it:

text
sum([3, 4])  -> 3     (correct: 7)
sum([5])     -> 0     (correct: 5)
sum([])      -> 0     (correct: 0)

The loop stops one item early because of the - 1. The empty-array case happens to be right, which is how this kind of bug survives a quick check. If you only tried [], you would have approved it. The fix is i < a.length.

Example 3: a missing edge case (realistic)

Requirement: maxOf(int[] a) returns the largest item; an empty array is invalid input and must throw IllegalArgumentException. The draft:

java
static int maxOf(int[] a) {
    int max = 0;
    for (int x : a) if (x > max) max = x;
    return max;
}

Results:

text
maxOf([-8, -3, -5]) -> 0   (correct: -3)
maxOf([])           -> 0   (correct: IllegalArgumentException)

Two edge cases are broken by one wrong assumption: the draft assumed the maximum is at least 0. The fix starts from the first item instead of from 0, and checks for an empty array first:

java
static int maxOf(int[] a) {
    if (a.length == 0) throw new IllegalArgumentException("empty array");
    int max = a[0];
    for (int x : a) if (x > max) max = x;
    return max;
}

Notice how the requirement decided what "correct" means for the empty array. The assistant had no way to guess that you wanted an exception rather than 0. Stating that in step 1 is your job.

Backend Insight

In a backend, wrong code rarely crashes loudly. A price calculation that is off by one cent per item, a "has the customer reached the order limit?" check that uses <= instead of <, or a loop that skips the last order line will all return normal-looking responses. The faults from this lesson are exactly the ones that reach production unnoticed.

Teams handle this with the same workflow you just learned, formalized: a written requirement (a ticket), a review (a pull request), and automated tests that run on every change. A reviewer asks "do you understand this code?" regardless of who typed it, and "an assistant wrote it" does not shorten the review. Later levels of this course apply your testing and Git skills to that team routine.

Common Mistakes

Mistake 1: "It compiles, so it works"

  • Incorrect: "No red lines in the IDE, so the draft is fine."
  • Correct: "It compiles. Now I trace it and run tests from the requirement."

Compiling only proves that every name exists and every type matches. Three of the four faults compile perfectly.

Mistake 2: Trying one example and stopping

A single happy-path run hides edge cases. In Example 2, the empty array passed. Always include a boundary (empty, one item, last item) and an invalid input.

Mistake 3: Using code you cannot explain

If a line is a mystery, you cannot know whether it is needed, wrong, or dangerous. Ask the assistant to explain it, then check the explanation against the documentation or the debugger, as you practiced in BEG-056. Delete the line if it is not needed.

Mistake 4: Asking for the code without a requirement

Without a written requirement you cannot say what "wrong" means. Write the requirement first, including the behaviour for empty, null, and invalid input.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. Name the four common faults in generated code, in your own words.

Show solution A1

A1. Off-by-one errors (a loop or index wrong by one), missing edge cases (empty, null, negative, all equal), wrong complexity (correct but too slow, for example hidden nested loops), and invented library calls (methods that do not exist).

A2. Each of these generated lines fails to compile. Why? Say which fault family it belongs to. (a) text.reverse() where text is a String. (b) Collections.sortDescending(list). (c) int n = numbers.size(); where numbers is an int[].

Show solution A2

A2. All three are invented calls, caught by the compiler. (a) String has no reverse(); StringBuilder does, so use new StringBuilder(text).reverse().toString(). (b) Collections has no sortDescending; sort with a reversed comparator, as in BEG-032. (c) Arrays have a length field, not a size() method: numbers.length. We compiled all three lines and got "cannot find symbol" for each.

A3. You paste an assistant's code into your assignment and it contains a bug. Who is responsible, and why?

Show solution A3

A3. You are. You chose to submit it, and the tool has no accountability. Courses and employers judge the code you hand in, whoever drafted it.

Level B: Application

B1. Requirement: average(int[] a) returns the average as a double; an empty array throws IllegalArgumentException. The draft is:

java
static double average(int[] a) {
    int s = 0;
    for (int x : a) s += x;
    return s / a.length;
}

Find two defects by tracing [1, 2] and []. Then write the corrected method.

Show solution B1

B1. Tracing [1, 2]: s becomes 3, and 3 / 2 is integer division, which gives 1 (we ran it: 1, where the correct answer is 1.5). For [2, 4, 5] it gives 3 instead of 3.666... For [] it divides by zero and throws ArithmeticException: / by zero instead of the required IllegalArgumentException. The fix:

java
static double average(int[] a) {
    if (a.length == 0) throw new IllegalArgumentException("empty array");
    int s = 0;
    for (int x : a) s += x;
    return (double) s / a.length;
}

B2. Put these workflow steps in the right order and say what you produce in each: Explain, Test, Requirement, Read, Fix, Draft.

Show solution B2

B2. Requirement (a written sentence), Draft (the assistant's code), Read (your notes on what it does), Test (tests written from the requirement), Fix (corrected code), Explain (a comment or README note in your words, plus any required disclosure).

B3. A classmate says: "I asked the assistant for a method, ran it with one example, it printed the right answer, so I am done." Give two reasons that is not enough.

Show solution B3

B3. Any two of: one example cannot show edge cases or boundaries; a correct answer on one input does not prove the logic is right (Example 2 passes for an empty array); they have not traced or explained the code; a requirement was never written, so "correct" is undefined.

Level C: Challenge

C1. The requirement is: countEven(int[] a) returns how many items are even. The draft is below. Run it by hand on [2, 4, 5], say what happens, and name the fault family.

java
static int countEven(int[] a) {
    int c = 0;
    for (int i = 0; i <= a.length; i++)
        if (a[i] % 2 == 0) c++;
    return c;
}
Show solution C1

C1. The loop uses i <= a.length, so the last iteration reads a[3] on an array of length 3. We ran it and got ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3. It is an off-by-one error. The fix is i < a.length.

C2. Choose a method you wrote in an earlier module. Write a requirement for it of at most three sentences, including one edge case. Say which of the four faults you would be most worried about in a generated version, and why.

Show solution C2

C2. Answers vary. A good answer states the input, the output, and what happens for the edge case, for example "isPalindrome(String s) returns true when s reads the same in both directions, ignoring case; null throws IllegalArgumentException", and names a concrete worry such as a missing edge case for the empty string.

Mini Challenge

Pick one small method you could ask an assistant to write, for example "return the smallest item in an int[]" or "return true if a string is a valid PIN of exactly four digits". Do not ask the assistant yet.

  1. Write the requirement in at most three sentences, including empty, null, or invalid input.
  2. Predict one bug in each of the four fault families that a careless draft could contain.
  3. Write down the six workflow steps and mark which one you have completed so far.

Check your work: your requirement names an edge case, each prediction would really be a bug for your method (not a vague worry), and you have completed only step 1.

Summary & Next Lesson

Key takeaways

  • Generated code is plausible, not guaranteed. Treat it as a draft that you proofread.
  • Four faults to look for: off-by-one errors, missing edge cases, wrong complexity, and invented library calls. Only the last is caught by the compiler.
  • You are responsible for any code you submit or ship, whoever wrote it.
  • If you cannot explain a line, you cannot use it.
  • Workflow: requirement, draft, read, test, fix, explain.

Next lesson

The next lesson, "Reading and tracing code you did not write", teaches the "read" step: a trace table you fill in by hand, how to check the edges, and how to spot hidden loops that make code slow.

Self-check

  1. Why can code that compiles still be wrong?
  2. Which of the four faults does the compiler catch?
  3. Who is responsible for generated code that you submit?
  4. Why must the requirement be written before asking for the draft?
  5. In Example 2, why did sum([]) return the right answer even though the method was wrong?
Show answers
  1. Compiling only checks that names and types are valid. Logic faults such as wrong loop bounds or missing cases compile fine.
  2. Invented library calls (a method that does not exist).
  3. You are.
  4. Without a requirement you cannot judge what "correct" means, including what to do for empty or invalid input.
  5. With no items the loop never runs, so the starting value 0 happens to match the correct answer. A single easy input can hide a bug.

References

  • GitHub Docs, Responsible use of GitHub Copilot code completion. It says suggestions can look valid while being wrong or not matching your intent, and that users should review, test, and validate them before accepting.
  • Oracle, Java SE 21 String API. It documents isEmpty() and isBlank() and shows that String has no static isNullOrEmpty. (The "since Java 11" note for isBlank is from the JDK documentation as we remember it; the page we fetched was truncated before that detail.)
  • Oracle, Java SE 21 List API. It shows getFirst() (since Java 21, throws NoSuchElementException on an empty list) and no firstOrNull. That firstOrNull exists in Kotlin is from general knowledge and was not verified here.
  • All code and outputs in this lesson were compiled and run with JDK 21.

Practice

Apply this lesson with the module's lab, exercises and quiz.

Lab and exercises Quiz

Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.

Continue to next lesson

Type to search levels, phases, modules and lessons.