Generated code is a draft, not an answer
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
| Fault | What it looks like | Who catches it |
|---|---|---|
| Off-by-one error | A loop starts at 1 instead of 0, stops one item early, or uses <= where < is needed | A trace or a boundary test |
| Missing edge case | Fails for an empty array, null, a negative number, or all-equal values | A test for that input |
| Wrong complexity | Correct answers, but far too slow on large input, for example contains inside a loop | A size test, or reading the loops |
| Invented library call | Uses a method that does not exist, or exists in another language or library | The 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
- 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. - Draft. Ask the assistant. Give it the requirement (BEG-056 L03 covered how to ask).
- Read. Read the draft once for the idea, then trace it by hand (the next lesson).
- Test. Run tests that you wrote from the requirement (lesson 3).
- Fix. When a test fails, decide whether the code or your test is wrong by rereading the requirement, then fix and rerun.
- 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:
static String firstName(List<String> names) {
return names.firstOrNull();
}
static boolean blank(String s) {
return String.isNullOrEmpty(s);
}Compiling this with JDK 21 gives:
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.StringBoth 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:
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:
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:
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:
static int maxOf(int[] a) {
int max = 0;
for (int x : a) if (x > max) max = x;
return max;
}Results:
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:
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:
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:
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.
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.
- Write the requirement in at most three sentences, including empty,
null, or invalid input. - Predict one bug in each of the four fault families that a careless draft could contain.
- 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
- Why can code that compiles still be wrong?
- Which of the four faults does the compiler catch?
- Who is responsible for generated code that you submit?
- Why must the requirement be written before asking for the draft?
- In Example 2, why did
sum([])return the right answer even though the method was wrong?
Show answers
- Compiling only checks that names and types are valid. Logic faults such as wrong loop bounds or missing cases compile fine.
- Invented library calls (a method that does not exist).
- You are.
- Without a requirement you cannot judge what "correct" means, including what to do for empty or invalid input.
- 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
StringAPI. It documentsisEmpty()andisBlank()and shows thatStringhas no staticisNullOrEmpty. (The "since Java 11" note forisBlankis from the JDK documentation as we remember it; the page we fetched was truncated before that detail.) - Oracle, Java SE 21
ListAPI. It showsgetFirst()(since Java 21, throwsNoSuchElementExceptionon an empty list) and nofirstOrNull. ThatfirstOrNullexists 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.
Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.