A 60-minute assignment-completion lesson in 62 slides. It defines a recursive method as a base case plus a recursive step, traces printArray by hand on a three-element array so you can watch the call stack grow and unwind, and then scaffolds the graded assignment in eight steps: a 100-element array filled by a for loop with rand.nextInt(100) + 1 and printed space-separated by a recursive printArray. Five traps cover the crashes that actually happen - deleting the base case and hitting ArrayIndexOutOfBoundsException, stopping one element early, expecting nextInt(100) to include 100 when it returns 0 through 99, using println and printing a column instead of a row, and forgetting the + 1, which produces a StackOverflowError past a thousand frames. The deck ends with a section on screenshots and submission, on the principle that you only get credit for what you demonstrate, and two extensions that prove you own the material: printing backwards by swapping two lines, and writing a recursive sum. Every snippet, output, error message, and stack trace was compiled and executed under OpenJDK Temurin 21.0.11.
Subject: Java · 103 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
Objectives
So far, your programs have been methods calling other methods. Today a method calls itself - and by the end of the hour, your graded assignment is finished and screenshotted.
1. Say what a recursive method is, and point to its two required parts: the base case and the recursive step.
2. Trace a recursive printArray on a 3-element array by hand, drawing the call stack as it grows and unwinds.
3. Build the full assignment: a 100-element array, filled by a for loop with random numbers from 1 to 100 inclusive, printed space-separated by a recursive printArray().
4. Predict and recognize the two classic recursion crashes: ArrayIndexOutOfBoundsException and StackOverflowError.
5. Capture the screenshots the grader requires - and prove you own the idea by printing the array backwards with a 2-line change.
Concept
Before writing anything, translate each sentence of the assignment into the code it is really asking for.
| the assignment says | which means |
|---|---|
Write a recursive method printArray() | A method that calls itself - no loop inside it |
| Displays all the elements, separated by spaces | System.out.print(... + " ") - print, not println |
| The array must be 100 elements in size | int[] array = new int[100]; |
| Filled using a for loop and a random number generator | A normal for loop + java.util.Random |
| Random number between 1 and 100 inclusive | rand.nextInt(100) + 1 |
| Screenshots show your program running | Plan the capture before the session ends |
Notice: only one method has to be recursive. The filling loop is a plain for loop you already know - the assignment says so explicitly.
Comparison
Comparison matrix
From The assignment, decoded: refill the which means column from what you know. The rest of the table is as it appeared.
| the assignment says | which means |
|---|---|
Write a recursive method printArray() | A method that calls itself - no loop inside it |
| Displays all the elements, separated by spaces | System.out.print(... + " ") - print, not println |
| The array must be 100 elements in size | int[] array = new int[100]; |
| Filled using a for loop and a random number generator | A normal for loop + java.util.Random |
| Random number between 1 and 100 inclusive | rand.nextInt(100) + 1 |
| Screenshots show your program running | Plan the capture before the session ends |
Concept
| minutes | what we do |
|---|---|
| 0-10 | What recursion is: base case + recursive step |
| 10-20 | Trace a tiny example by hand - the highest-value 10 minutes |
| 20-45 | Build the assignment, step by step, in your editor |
| 45-52 | Run it and capture the required screenshots |
| 52-60 | Prove you own it: print backwards, preview recursive sum |
The screenshots are the grade, so they are scheduled inside the hour - not left for later.
Trade off
Comparison matrix
From How we will spend the hour: every row here is a choice with a cost. Fill the what we do column, then say which row you would actually pick and what you give up for it.
| minutes | what we do |
|---|---|
| 0-10 | What recursion is: base case + recursive step |
| 10-20 | Trace a tiny example by hand - the highest-value 10 minutes |
| 20-45 | Build the assignment, step by step, in your editor |
| 45-52 | Run it and capture the required screenshots |
| 52-60 | Prove you own it: print backwards, preview recursive sum |
Section
Concept
How would you print every element of an array with a loop? You could write this in your sleep:
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}On the array {7, 2, 9} the loop does three passes and stops when i reaches 3:
| pass | i | prints |
|---|---|---|
| 1 | 0 | 7 |
| 2 | 1 | 2 |
| 3 | 2 | 9 |
| - | 3 | loop condition false - stop |
Keep this trace in your head. Recursion will do the exact same four things - just without a loop.
Counterexample
Discussion prompt
How would you print every element of an array with a loop? You could write this in your sleep:
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
On the array {7, 2, 9} the loop does three passes and stops when i reaches 3:
Intuition
Imagine a line of people who need to count off. Nobody runs a loop. Each person follows one tiny rule: say your number, then tap the next person.
The last person has a different rule: if there is nobody behind you, stop. Without that person, the tapping would never end.
That is all recursion is: everyone runs the same rule, each on a slightly smaller remaining line, and one special case stops the chain.
Analogy
Discussion prompt
Explain Counting off: recursion with people by analogy to something with no Java in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Imagine a line of people who need to count off. Nobody runs a loop. Each person follows one tiny rule: say your number, then tap the next person.
Concept
A recursive method is a method that calls itself - directly, or indirectly through another method. Each call handles one small piece and hands the rest to a fresh call of the same method.
| call | handles | hands off |
|---|---|---|
| printArray(arr, 0) | element 0 | the rest, starting at 1 |
| printArray(arr, 1) | element 1 | the rest, starting at 2 |
| printArray(arr, 2) | element 2 | the rest, starting at 3 |
| printArray(arr, 3) | nothing left | nobody - it stops |
"Print the array starting at index 0" becomes: print element 0, then print the array starting at index 1. Same job, smaller problem.
Explain it
Discussion prompt
Explain A method that calls itself to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
A recursive method is a method that calls itself - directly, or indirectly through another method. Each call handles one small piece and hands the rest to a fresh call of the same method.
Concept
Base case — The condition under which the method does NOT call itself - it just stops (returns). For printArray: the index has walked past the last element.
Recursive step — The line where the method calls itself on a smaller version of the problem. For printArray: print one element, then call yourself with index + 1.
Every recursive method you will ever write has both. Missing base case: it never stops. A recursive step that does not shrink the problem: it never stops. We will crash both ways today - on purpose.
Definition probe
Sort into buckets
Every line below is part of the definition of Base case or of Recursive step — one or the other, never both. Put each where it belongs.
Pattern
Memorize this shape. Every recursive method today (and most you will ever write) is this skeleton with the blanks filled in:
static void doJob(int[] array, int index) {
if (index == array.length) { // 1. base case: STOP
return;
}
// 2. do ONE small piece of work here
doJob(array, index + 1); // 3. recursive step: the REST
}| piece | job |
|---|---|
if (index == array.length) | base case - detects there is nothing left to do |
return; | stops this call without calling again |
| the work line | handles exactly one element |
doJob(array, index + 1) | same method, smaller problem |
+ 1 | the shrinking - without it, the problem never gets smaller |
Order matters: check the base case first, then work, then recurse. We always write the base case before anything else.
Edge cases
Discussion prompt
The recursion skeleton works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".
Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.
Answer:
Memorize this shape. Every recursive method today (and most you will ever write) is this skeleton with the blanks filled in:
Intuition
Each recursive call must be given a strictly smaller problem than the one it received - here, an index one closer to the end.
Shrinking problem + a base case waiting at the bottom = guaranteed to finish. It is the same promise a for loop makes with i++ and its stopping condition - just written as a method.
When a recursive method misbehaves, ask exactly two questions: Is there a base case? Does every call move toward it? One of the two answers is always no.
Trap
"The recursion will just stop on its own when the array runs out, right?" Let's delete the base case and find out:
static void printArray(int[] array, int index) {
System.out.print(array[index] + " ");
printArray(array, index + 1); // no base case!
}On {7, 2, 9} it prints 7 2 9 - and then the fourth call executes array[3] on a 3-element array. Compiled and run, it crashes with exactly this:
| what the real run printed | why |
|---|---|
7 2 9 | the first three calls worked fine |
ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3 | call number 4 read past the end of the array |
at NoBaseCase.printArray(NoBaseCase.java:4) | the crash is on the array-read line, not the recursive call |
The base case if (index == array.length) return; exists to fire before array[index] runs. It is the bouncer at the door - remove it and the very next call walks off the end.
Elimination
Eliminate the wrong options
In printArray, what is the job of the line if (index == array.length) { return; }?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: C
Why: That line is the base case. Valid indexes run from 0 to array.length - 1, so the moment index equals array.length there is nothing left to print, and returning without another recursive call stops the whole chain.
Check
Check your understanding
In printArray, what is the job of the line if (index == array.length) { return; }?
Answer: C
Why: That line is the base case. Valid indexes run from 0 to array.length - 1, so the moment index equals array.length there is nothing left to print, and returning without another recursive call stops the whole chain.
Section
Estimation
Predict first
Never trace recursion for the first time on 100 elements. We shrink the problem to 3 elements, trace it perfectly, and then trust the same mechanism at 100.
Commit before you compute: what does Our tiny test array come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Name what we know before running anything
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. arr.length is 3, valid indexes are 0, 1, 2, and the first call starts the index at 0.
Worked example
Never trace recursion for the first time on 100 elements. We shrink the problem to 3 elements, trace it perfectly, and then trust the same mechanism at 100.
int[] arr = {7, 2, 9};
printArray(arr, 0); // expected output: 7 2 9Name what we know before running anything
Why: arr.length is 3, valid indexes are 0, 1, 2, and the first call starts the index at 0.
| index | value |
|---|---|
| 0 | 7 |
| 1 | 2 |
| 2 | 9 |
Predict before you trace: how many calls to printArray will happen in total? Lock in a number - the trace will confirm or correct it.
Pattern
Step through it
Step through Our tiny test array one row at a time. What is driving the change, and what would the row after the last one be?
Ranking
Put in order
Put the moves of Call 1: printArray(arr, 0) into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. No - so we do NOT stop. We fall through to the work line.
Worked example
static void printArray(int[] array, int index) {
if (index == array.length) {
return;
}
System.out.print(array[index] + " ");
printArray(array, index + 1);
}Check the base case: is 0 == 3?
Why: No - so we do NOT stop. We fall through to the work line.
| question | answer |
|---|---|
| index == array.length? | 0 == 3 is false - keep going |
| work line prints | 7 (that is arr[0]) |
| then calls | printArray(arr, 1) |
Print one element: arr[0] is 7
Why: Each call handles exactly one element - this call's element is the one at its own index.
Hand off the rest: printArray(arr, 1)
Why: Call 1 is now PAUSED on line 6, waiting for the hand-off to finish. It has not returned yet.
Error analysis
Annotate
Walk the callouts on Call 1: printArray(arr, 0). Each one is a place this is easy to get subtly wrong.
Missing information
Discussion prompt
Three calls are now paused, each waiting on the one it started. The output so far reads 7 2 9 - and one more call is in flight.
What do you need to know — or decide — before the first line can be written? List everything the problem has to hand you.
Hint: Anything you would have to invent to get started is a thing the problem must supply.
Answer:
1 == 3 is false, prints arr[1] which is 2, then calls printArray(arr, 2) and pauses.
Worked example
Call 2: printArray(arr, 1)
Why: 1 == 3 is false, prints arr[1] which is 2, then calls printArray(arr, 2) and pauses.
Call 3: printArray(arr, 2)
Why: 2 == 3 is false, prints arr[2] which is 9, then calls printArray(arr, 3) and pauses.
| call | base case? | prints | then calls |
|---|---|---|---|
| printArray(arr, 0) | 0 == 3? no | 7 | printArray(arr, 1) |
| printArray(arr, 1) | 1 == 3? no | 2 | printArray(arr, 2) |
| printArray(arr, 2) | 2 == 3? no | 9 | printArray(arr, 3) |
Three calls are now paused, each waiting on the one it started. The output so far reads 7 2 9 - and one more call is in flight.
Reverse engineer
Discussion prompt
Work backwards. The example finished here:
Call 3: printArray(arr, 2)
What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.
Hint: Every quantity in the result had to enter somewhere. Account for each one.
Answer:
Three calls are now paused, each waiting on the one it started. The output so far reads 7 2 9 - and one more call is in flight.
Concept
Call 4 is printArray(arr, 3). This time the base-case check asks: is 3 == 3? Yes. The method returns immediately - it prints nothing and starts no new call.
| call | base case? | prints |
|---|---|---|
| printArray(arr, 3) | 3 == 3? YES | nothing - just return |
So the answer to the prediction: 4 calls for 3 elements. Always one extra - the quiet call whose only job is to say "we're done."
Concept
Java keeps track of the paused calls on the call stack. Every call gets a frame; the frame stays until that call returns. At the deepest moment, our stack looks like this:
| stack (top = most recent) | state |
|---|---|
| printArray(arr, 3) | checking base case - about to return |
| printArray(arr, 2) | paused: printed 9, waiting |
| printArray(arr, 1) | paused: printed 2, waiting |
| printArray(arr, 0) | paused: printed 7, waiting |
| main | paused: waiting for printArray(arr, 0) |
For the real assignment this tower is 101 printArray frames tall. Same picture, taller stack - Java handles thousands of frames without complaint.
Concept
Now the returns cascade. Call 4 returns to call 3. Call 3 has nothing left after its recursive call, so it returns to call 2 - and so on down the tower.
| step | what returns | stack height after |
|---|---|---|
| 1 | printArray(arr, 3) finishes | 3 printArray frames |
| 2 | printArray(arr, 2) finishes | 2 printArray frames |
| 3 | printArray(arr, 1) finishes | 1 printArray frame |
| 4 | printArray(arr, 0) finishes | 0 - we are back in main |
Nothing prints during the unwind because the recursive call is the last line of the method. File that away - it becomes interesting in Part 6 when we move a line and the unwind starts printing.
Intuition
Put the recursion trace next to the loop trace from Part 1. They are the same movie:
| moment | the loop did | the recursion did |
|---|---|---|
| handle element 0 | pass 1: i = 0, prints 7 | call 1: index 0, prints 7 |
| handle element 1 | pass 2: i = 1, prints 2 | call 2: index 1, prints 2 |
| handle element 2 | pass 3: i = 2, prints 9 | call 3: index 2, prints 9 |
| notice we are done | i = 3: condition false, exit | call 4: base case, return |
The loop's i++ became index + 1; the loop's condition became the base case. Recursion is not a new kind of repetition - it is the same repetition, carried by method calls instead of a loop header.
Prediction
Predict first
printArray(arr, 0) runs on the 3-element array {7, 2, 9}. How many times is printArray called in total, counting the first call?
Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.
Correct: 4 - one per element, plus the base-case call that prints nothing.
Why: Calls at indexes 0, 1 and 2 each print an element, and the fourth call at index 3 hits the base case and returns silently. An n-element array always costs exactly n + 1 calls - the assignment's 100-element array makes 101.
Check
Check your understanding
printArray(arr, 0) runs on the 3-element array {7, 2, 9}. How many times is printArray called in total, counting the first call?
Answer: B
Why: Calls at indexes 0, 1 and 2 each print an element, and the fourth call at index 3 hits the base case and returns silently. An n-element array always costs exactly n + 1 calls - the assignment's 100-element array makes 101.
Trap
"Stop AT the last element" sounds right - so this base case is tempting:
if (index == array.length - 1) { // stop at the last one?
return;
}
System.out.print(array[index] + " ");
printArray(array, index + 1);Compiled and run on {7, 2, 9}, this prints 7 2 - the 9 silently vanishes. The call at index 2 returns before printing, but index 2 still had work to do.
| call | with length - 1 | should be |
|---|---|---|
| printArray(arr, 0) | prints 7 | prints 7 |
| printArray(arr, 1) | prints 2 | prints 2 |
| printArray(arr, 2) | 2 == 2, returns - prints NOTHING | prints 9 |
The base case marks the first index with no work left - and index array.length - 1 still has one element to print. Stop at array.length, one past the end. No crash, no error message - just a quietly wrong answer, which is why you must count the output, not just admire it.
Check
Check your understanding
With the base case written as if (index == array.length - 1) return;, what does printArray(arr, 0) output on the array {7, 2, 9}?
Answer: B
Why: The call at index 2 sees 2 == 3 - 1 and returns before reaching the print line, so the last element is silently skipped. The verified run prints exactly '7 2'. Off-by-one base cases do not crash - they quietly drop an element.
Section
Concept
From here on, you type, I prompt. We build in eight steps, and after every step the file still compiles. If we get lost, we recompile and look.
| step | what gets written | part of the assignment |
|---|---|---|
| 1 | class + empty main | scaffolding |
| 2 | the 100-element array | 'must be 100 elements in size' |
| 3 | Random + the fill loop | 'filled using a for loop and a random number generator' |
| 4-7 | the recursive printArray | 'write a recursive method printArray()' |
| 8 | call it from main, run it | 'displays all the elements' |
Steps 2 and 3 are material you already know - we do them first to bank a quick win before the new idea.
Worked example
import java.util.Random;
public class PrintArrayRecursion {
public static void main(String[] args) {
// everything starts here
}
}Import Random at the very top
Why: java.util.Random is not auto-imported; forgetting this line is the most common first compile error today.
| line | purpose |
|---|---|
import java.util.Random; | makes the Random class usable by its short name |
public class PrintArrayRecursion | class name must match the file name exactly |
public static void main(...) | where Java starts running |
Compile now, before writing more
Why: A file that compiles every step means any new error was caused by the last five lines you typed - nothing else.
Blank canvas
Draw it
Draw what Step 1 · Class skeleton and main just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Estimation
Predict first
Indexes 0 to 99 - not 1 to 100. This same one-past-the-end boundary is exactly where the recursive base case will stand guard later: at index 100.
Commit before you compute: what does Step 2 · Instantiate the 100-element array come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Read the line right to left
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. new int[100] builds a block of 100 int slots, all starting at 0; int[] array names it.
Worked example
int[] array = new int[100];Read the line right to left
Why: new int[100] builds a block of 100 int slots, all starting at 0; int[] array names it.
| fact | value |
|---|---|
| array.length | 100 - fixed forever |
| valid indexes | 0 through 99 |
| every slot right now | 0 (Java's default for int) |
Indexes 0 to 99 - not 1 to 100. This same one-past-the-end boundary is exactly where the recursive base case will stand guard later: at index 100.
Sorting
Sort into buckets
These are the pieces of Recursion - Building the printArray() Assignment, out of order. Put each one back under the part of the lesson it belongs to.
Concept
One Random object serves the whole program. Ask it for numbers with nextInt(n) - which hands back a value from 0 to n - 1, never n itself.
Random rand = new Random();
int roll = rand.nextInt(100); // 0..99 - note: NOT 1..100| expression | range it produces |
|---|---|
rand.nextInt(100) | 0 to 99 |
rand.nextInt(100) + 1 | 1 to 100 - what the assignment wants |
rand.nextInt(101) | 0 to 100 - zero is not allowed here |
rand.nextInt(6) + 1 | 1 to 6 - a die, same recipe |
The recipe for "a to b inclusive" is always rand.nextInt(b - a + 1) + a. Today: rand.nextInt(100) + 1.
Ranking
Put in order
Put the moves of Step 3 · Fill the array into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. One generator is reused for all 100 numbers - creating it inside the loop works but is wasteful and a bad habit.
Worked example
Random rand = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = rand.nextInt(100) + 1; // 1..100 inclusive
}Create Random once, OUTSIDE the loop
Why: One generator is reused for all 100 numbers - creating it inside the loop works but is wasteful and a bad habit.
Loop i from 0 while i < array.length
Why: The standard fill pattern touches indexes 0 through 99 - exactly the valid ones.
Store nextInt(100) + 1 each pass
Why: Verified run: first slots received 65, 41, 38 - every value landed inside 1..100.
| pass | i | stored (verified run) |
|---|---|---|
| 1 | 0 | 65 |
| 2 | 1 | 41 |
| 3 | 2 | 38 |
| ... | ... | ... 100 values total, all in 1..100 |
Comparison
Comparison matrix
From Step 3 · Fill the array: refill the i column from what you know. The rest of the table is as it appeared.
| pass | i | stored (verified run) |
|---|---|---|
| 1 | 0 | 65 |
| 2 | 1 | 41 |
| 3 | 2 | 38 |
| ... | ... | ... 100 values total, all in 1..100 |
Trap
The assignment says 1 to 100 inclusive, and 100 is right there in the call - so this looks done:
array[i] = rand.nextInt(100); // "between 1 and 100"... right?nextInt(100) produces 0 to 99. Sooner or later a 0 lands in the output - and a screenshot with a 0 in it is documented proof the requirement was missed.
| version | range | verdict |
|---|---|---|
rand.nextInt(100) | 0 to 99 | 0 is possible, 100 is impossible - both wrong |
rand.nextInt(100) + 1 | 1 to 100 | matches the assignment exactly |
rand.nextInt(101) | 0 to 100 | still lets 0 through |
The bound in nextInt(n) is exclusive: n values starting at 0. Shift the whole window up with + 1 and the range becomes 1 to 100.
Break the constraint
Discussion prompt
The rule this trap just fixed:
The bound in nextInt(n) is exclusive: n values starting at 0. Shift the whole window up with + 1 and the range becomes 1 to 100.
Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?
Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.
Commit first
Predict first
Which expression produces a random integer between 1 and 100 inclusive, as the assignment requires?
Commit to an answer, then rate it — certain, fairly sure, or guessing — and write the rating down before you turn the page.
Correct: rand.nextInt(100) + 1
Why: nextInt(100) yields 0 through 99 - one hundred values starting at zero. Adding 1 shifts the whole window to 1 through 100, hitting both endpoints the assignment demands and nothing outside them.
The rating matters as much as the answer: confident-and-wrong is the combination that survives revision, because nothing about it feels like it needs revisiting.
Check
Check your understanding
Which expression produces a random integer between 1 and 100 inclusive, as the assignment requires?
Answer: C
Why: nextInt(100) yields 0 through 99 - one hundred values starting at zero. Adding 1 shifts the whole window to 1 through 100, hitting both endpoints the assignment demands and nothing outside them.
Worked example
Below main, start the star of the show. It needs the array AND a way to know which element is mine - that is the index parameter:
public static void printArray(int[] array, int index) {
// base case first - next step
}| parameter | why it must be there |
|---|---|
int[] array | every call needs to see the same array |
int index | each call's private marker: which element THIS call handles |
(return type void) | the job is printing, not computing a value |
The pseudo-code shows one argument - printArray(integer array). We will honor that exactly in Step 7 with a one-line trick. The index parameter is what makes the recursion possible at all.
Worked example
public static void printArray(int[] array, int index) {
if (index == array.length) { // past the last element?
return; // then we are done - stop
}
}Write the stop before the go
Why: A recursive method without its base case is a crash waiting to happen - Part 1 proved it with a real ArrayIndexOutOfBoundsException. Writing it first makes the crash impossible.
| index arriving | base case says |
|---|---|
| 0 through 99 | not yet - there is work to do |
| 100 | 100 == array.length: stop, print nothing |
Compare with ==, stop at exactly array.length
Why: Part 2's trap showed length - 1 silently eats the last element. The first index with NO work left is length itself.
Pattern
Predict first
The table runs: System.out.print(array[index] + " ") | 65 41 38 ... - one row, space-separated · System.out.println(array[index]) | one number per line - 100 rows
In Step 6 · Do one small piece of work, given the rows so far: what is the next one — the row where choice is System.out.print(array[index])?
Correct: System.out.print(array[index]) | 654138... - unreadable digit soup
| choice | output shape |
|---|---|
System.out.print(array[index] + " ") | 65 41 38 ... - one row, space-separated |
System.out.println(array[index]) | one number per line - 100 rows |
System.out.print(array[index]) | 654138... - unreadable digit soup |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. The assignment says separated by SPACES - println would put every element on its own line.
Worked example
public static void printArray(int[] array, int index) {
if (index == array.length) {
return;
}
System.out.print(array[index] + " "); // MY one element
}print, not println
Why: The assignment says separated by SPACES - println would put every element on its own line.
Glue the space on with + " "
Why: Each element prints as the number followed by one space: 65 41 38 ... on a single line.
| choice | output shape |
|---|---|
System.out.print(array[index] + " ") | 65 41 38 ... - one row, space-separated |
System.out.println(array[index]) | one number per line - 100 rows |
System.out.print(array[index]) | 654138... - unreadable digit soup |
Trap
println is the habit your fingers know, and the program even looks like it works:
System.out.println(array[index]); // one element per LINEOne hundred numbers, one per line, scrolls the console for pages - and the assignment's phrase separated by spaces is not what the screenshot shows.
| method | when to use it |
|---|---|
print(x + " ") | building a row piece by piece - today's job |
println(x) | when each item deserves its own line |
println() with nothing | finishing a row: one clean newline at the end |
Polish: after the whole recursion finishes back in main, one bare System.out.println(); ends the row so the command prompt does not glue itself to your 100th number in the screenshot.
Worked example
// Matches the pseudo-code: printArray(integer array)
public static void printArray(int[] array) {
printArray(array, 0); // hand off to the worker, starting at 0
}
public static void printArray(int[] array, int index) {
if (index == array.length) {
return;
}
System.out.print(array[index] + " ");
printArray(array, index + 1); // the recursive step
}Add the recursive call with index + 1
Why: One element handled, the rest handed to a fresh call one step closer to the base case. This line is what makes the method recursive.
Add the one-argument version the pseudo-code shows
Why: Two methods, same name, different parameter lists - an OVERLOAD. The short one just starts the real one at index 0, so main can call printArray(array) exactly like the assignment's pseudo-code.
| call written | which version runs |
|---|---|
printArray(array) | the 1-arg starter - which immediately calls the worker |
printArray(array, 0) | the 2-arg recursive worker, from the top |
printArray(array, index + 1) | the 2-arg worker again - one element further along |
Trap
One missing character - passing index instead of index + 1 - and the method still compiles perfectly:
System.out.print(array[index] + " ");
printArray(array, index); // forgot the + 1Every call now hands off the same problem it received. Nothing ever moves toward the base case. The real run printed 7 7 7 7 7 ... and then died - with over a thousand identical stack frames:
| what the real run showed | meaning |
|---|---|
7 7 7 7 7 7 ... | index stays 0 forever - same element every call |
java.lang.StackOverflowError | the call stack ran out of room for new frames |
at NoProgress.printArray(NoProgress.java:8) x 1000+ | the same line, over a thousand times - the loop that never shrank |
This is the second recursion crash: the base case exists, but no call ever reaches it. Diagnosis rule - repeated output + StackOverflowError = the problem is not shrinking. Check the recursive call's arguments.
Two truths and a lie
Sort into buckets
Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.
Fill the middle
Fill in the blanks
From Step 8 · Wire it up in main — one line has had its right-hand side removed. Put it back.
public static void main(String[] args) rand.nextInt(100) + 1;}
}
printArray(array); // the recursive display
System.out.println(); // finish the row cleanly
}
Why: array[i] is what everything below it consumes, so the wrong expression here fails later and somewhere else. printArray can only display what the loop already stored - calling it before the loop would print 100 zeros.
Worked example
public static void main(String[] args) {
int[] array = new int[100];
Random rand = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = rand.nextInt(100) + 1;
}
printArray(array); // the recursive display
System.out.println(); // finish the row cleanly
}Fill first, print second
Why: printArray can only display what the loop already stored - calling it before the loop would print 100 zeros.
Call the one-argument printArray(array)
Why: Exactly the call the pseudo-code shows - the overload starts the recursion at index 0 for us.
| order | action | assignment requirement met |
|---|---|---|
| 1 | new int[100] | 100 elements in size |
| 2 | for loop + nextInt(100) + 1 | filled by loop + RNG, 1..100 inclusive |
| 3 | printArray(array) | recursive display, space-separated |
Concept
import java.util.Random;
public class PrintArrayRecursion {
// Matches the pseudo-code signature: printArray(integer array)
public static void printArray(int[] array) {
printArray(array, 0);
}
public static void printArray(int[] array, int index) {
if (index == array.length) { // base case: past the last element
return;
}
System.out.print(array[index] + " ");
printArray(array, index + 1); // recursive step: rest of the array
}
public static void main(String[] args) {
int[] array = new int[100];
Random rand = new Random();
for (int i = 0; i < array.length; i++) {
array[i] = rand.nextInt(100) + 1; // 1..100 inclusive
}
printArray(array);
System.out.println();
}
}| assignment requirement | where it lives |
|---|---|
| recursive method printArray() | the 2-arg method - calls itself on line with index + 1 |
| displays all elements, space-separated | System.out.print(array[index] + " ") |
| array of 100 elements | new int[100] in main |
| filled by for loop + RNG, 1..100 | the for loop with rand.nextInt(100) + 1 |
| pseudo-code call printArray(array) | the 1-arg overload |
Twenty-seven lines, every sentence of the assignment accounted for. This exact file compiled and ran under OpenJDK 21 - the next slide shows its real output.
Pattern
Predict first
The table runs: starts with | 65 41 38 58 13 96 29 9 66 61 ... · how many numbers | exactly 100 · all within 1..100? | yes - smallest seen 2, largest seen 100
In Compile and run, given the rows so far: what is the next one — the row where check on the real output is separated by?
Correct: separated by | single spaces, one row
| check on the real output | result from the verified run |
|---|---|
| starts with | 65 41 38 58 13 96 29 9 66 61 ... |
| how many numbers | exactly 100 |
| all within 1..100? | yes - smallest seen 2, largest seen 100 |
| separated by | single spaces, one row |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. IDE users: the Run button does both.
Worked example
javac PrintArrayRecursion.java
java PrintArrayRecursionjavac compiles, java runs
Why: IDE users: the Run button does both. Command-line users: two commands, in this order, from the folder containing the file.
| check on the real output | result from the verified run |
|---|---|
| starts with | 65 41 38 58 13 96 29 9 66 61 ... |
| how many numbers | exactly 100 |
| all within 1..100? | yes - smallest seen 2, largest seen 100 |
| separated by | single spaces, one row |
Count before you celebrate
Why: The early-stop trap taught us wrong recursion can LOOK fine. Verify the count: in a terminal, or paste the row into an editor and check. 100 numbers, no zeros, nothing above 100.
Blank canvas
Draw it
Draw what Compile and run just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Concept
Before any screenshot, audit the row the way the person grading it will. Each check corresponds to a requirement - and to one of the traps we defused:
| audit question | what a failure would mean |
|---|---|
| exactly 100 numbers? | an early-stop base case silently dropped some |
| any 0 anywhere? | nextInt(100) without the + 1 |
| anything above 100? | wrong bound in nextInt |
| one row, single spaces? | println crept back in |
| different numbers on a re-run? | Random not actually used |
Thirty seconds of auditing now beats resubmitting later. Correct-looking output that nobody counted is how the early-stop trap survives into submissions.
Check
Check your understanding
Which single line makes printArray a recursive method?
Answer: C
Why: A method is recursive precisely when it calls itself, and that is the line where printArray invokes printArray. The base case and the print line are essential supporting cast, but the self-call is what earns the word 'recursive' - and it is what the grader will look for in your code screenshot.
Section
Concept
Both recursion crashes announce themselves clearly if you read the first line. These are the verbatim messages from our real broken runs:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException:
Index 3 out of bounds for length 3
at NoBaseCase.printArray(NoBaseCase.java:4)
Exception in thread "main" java.lang.StackOverflowError
at NoProgress.printArray(NoProgress.java:8)
at NoProgress.printArray(NoProgress.java:8)
... (the same line, 1000+ times)| crash | translation | fix |
|---|---|---|
| ArrayIndexOutOfBoundsException | recursion ran PAST the end - base case missing or checking the wrong thing | guard with index == array.length before touching array[index] |
| StackOverflowError | recursion never moved - calls piled up until the stack was full | make sure the recursive call passes index + 1 |
| same line repeated 1000+ times in the trace | each repeat is one stacked call of your method | that repeated line number IS the bug's address |
The wall of repeated at ... lines is not noise - it is the call stack from Part 2, printed out. You already know how to read it.
Concept
Any recursive method, any language, any bug - start with the same two questions from Part 1:
| symptom | question that finds it | usual culprit |
|---|---|---|
| ArrayIndexOutOfBoundsException | Is there a base case, checked FIRST? | no base case, or it is below the array access |
| StackOverflowError + repeating output | Does every call move toward the base case? | recursive call passes index, not index + 1 |
| last element missing | Does the base case stop at exactly array.length? | length - 1: stops one early, silently |
| prints 100 zeros | Did the fill loop run before printArray? | printArray called above the for loop |
Note the fourth row crashes nothing and looks structurally fine - output you did not actually read is the failure mode screenshots are designed to catch.
Prediction
Predict first
A student's program prints '43 43 43 43 43 ...' and then crashes with java.lang.StackOverflowError. Which bug is the most likely cause?
Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.
Correct: The recursive call passes index instead of index + 1.
Why: The same value repeating means the index never advances - every call re-prints its own element and hands off an identical problem, so calls pile up until the stack overflows. Our verified broken run showed exactly this shape: repeated output, then over a thousand identical stack frames.
Check
Check your understanding
A student's program prints '43 43 43 43 43 ...' and then crashes with java.lang.StackOverflowError. Which bug is the most likely cause?
Answer: C
Why: The same value repeating means the index never advances - every call re-prints its own element and hands off an identical problem, so calls pile up until the stack overflows. Our verified broken run showed exactly this shape: repeated output, then over a thousand identical stack frames.
Section
Concept
That sentence is in the assignment, verbatim. The grader will not run your code - the screenshots ARE the submission. So we capture them deliberately, against a checklist, before the session ends.
| evidence required | which screenshot provides it |
|---|---|
| the method is actually recursive | code screenshot: the self-call visible, no loop in printArray |
| array is 100 elements, filled by loop + RNG | code screenshot: new int[100] and the for loop visible |
| program runs and displays all elements | run screenshot: the full row of 100 numbers |
| values are 1..100, space-separated | run screenshot: readable numbers, no zeros |
| the numbers are genuinely random | second run screenshot: different numbers |
Three screenshots total: the code, one run, a second run. Five minutes of careful capture protects the whole hour of work.
Concept
Frame the shot so the grader can verify recursion at a glance - the whole printArray method in view, nothing cropped.
| must be visible | why the grader cares |
|---|---|
| both printArray methods, complete | the self-call with index + 1 is the proof of recursion |
| no for/while inside printArray | a loop in there means the recursion requirement was dodged |
| main: new int[100] + the fill loop | the 100-size and loop+RNG requirements |
| the file name / class name | ties the code to the run screenshots |
If your editor font is small, zoom in before capturing - an unreadable screenshot demonstrates nothing, and the assignment is explicit about what that earns.
Explain it
Discussion prompt
Explain Screenshot 1 · The code to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
Frame the shot so the grader can verify recursion at a glance - the whole printArray method in view, nothing cropped.
Concept
Now the program running - the part the assignment stresses. All 100 numbers must be visible and countable.
| capture step | detail |
|---|---|
| 1. widen the console window first | 100 numbers wrap across only 2-3 lines when the window is wide |
| 2. run, and capture command + output together | the java PrintArrayRecursion line above the output proves what produced it |
| 3. scan before you snap | no 0, nothing over 100, single spaces - the traps we defused |
| 4. run AGAIN and capture the second output | different numbers = the random generator is real, not hard-coded |
The two-run pair is the cheapest insurance in the submission: it preempts the one question a skeptical grader always has - did the RNG actually run?
Analogy
Discussion prompt
Explain Screenshots 2 and 3 · The runs by analogy to something with no Java in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
The two-run pair is the cheapest insurance in the submission: it preempts the one question a skeptical grader always has - did the RNG actually run?
Concept
A narrow console wraps or scrolls 100 numbers, and a screenshot missing part of the output demonstrates only part of the assignment. Fallbacks, in order of preference:
| option | how |
|---|---|
| widen the window first | maximize the terminal/IDE console before running - usually fits in 2-3 lines |
| zoom out one step | Ctrl+Minus in most terminals and IDE consoles shrinks the font enough |
| take two overlapping screenshots | shot 1: command + first half; shot 2: second half + the prompt returning |
| scroll-capture | some tools capture the whole scrollback as one tall image |
Whatever you choose, the pair (command that ran) + (complete output) must be reconstructible by the grader from your images alone.
Counterexample
Discussion prompt
A narrow console wraps or scrolls 100 numbers, and a screenshot missing part of the output demonstrates only part of the assignment. Fallbacks, in order of preference:
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Answer:
Whatever you choose, the pair (command that ran) + (complete output) must be reconstructible by the grader from your images alone.
Section
Missing information
Discussion prompt
The assignment is done. Now the ownership test: make it print the array backwards - changing nothing but the order of two lines.
What do you need to know — or decide — before the first line can be written? List everything the problem has to hand you.
Hint: Anything you would have to invent to get started is a thing the problem must supply.
Answer:
Each call now stays quiet on the way down. The printing happens during the UNWIND - the return trip Part 2 said would become interesting.
Worked example
The assignment is done. Now the ownership test: make it print the array backwards - changing nothing but the order of two lines.
public static void printBackwards(int[] array, int index) {
if (index == array.length) {
return;
}
printBackwards(array, index + 1); // recurse FIRST
System.out.print(array[index] + " "); // print on the way BACK
}Recurse before printing
Why: Each call now stays quiet on the way down. The printing happens during the UNWIND - the return trip Part 2 said would become interesting.
| phase | call | prints |
|---|---|---|
| down | index 0 -> 1 -> 2 -> 3 (base case) | nothing yet |
| back up | call at index 2 resumes | 9 |
| back up | call at index 1 resumes | 2 |
| back up | call at index 0 resumes | 7 |
Verify against the real run
Why: Compiled and executed on {7, 2, 9}: output is exactly 9 2 7. With a loop this reversal means rewriting the header; with recursion it was a two-line swap.
Reverse engineer
Discussion prompt
Work backwards. The example finished here:
Verify against the real run
What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.
Hint: Every quantity in the result had to enter somewhere. Account for each one.
Answer:
The assignment is done. Now the ownership test: make it print the array backwards - changing nothing but the order of two lines.
Elimination
Eliminate the wrong options
Inside a correct printArray, the print line and the recursive call are swapped, so the method recurses first and prints after. Running it on {7, 2, 9} - what happens?
3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.
Survives elimination: B
Why: With the recursive call first, every call dives all the way to the base case before any printing happens; each call then prints its element as the stack unwinds, deepest call first. The verified run outputs exactly 9 2 7. Where a line sits relative to the recursive call decides whether it runs on the way down or the way back.
Check
Check your understanding
Inside a correct printArray, the print line and the recursive call are swapped, so the method recurses first and prints after. Running it on {7, 2, 9} - what happens?
Answer: B
Why: With the recursive call first, every call dives all the way to the base case before any printing happens; each call then prints its element as the stack unwinds, deepest call first. The verified run outputs exactly 9 2 7. Where a line sits relative to the recursive call decides whether it runs on the way down or the way back.
Estimation
Predict first
Same skeleton, different work: instead of printing each element, add it. This is the from-scratch exercise to try solo after the session:
Commit before you compute: what does Transfer: a recursive sum come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.
Correct: Verify: real run on {7, 2, 9} returned 18
Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Read the table bottom-up to watch the answers assemble during the unwind - the same return trip that printed backwards a moment ago.
Worked example
Same skeleton, different work: instead of printing each element, add it. This is the from-scratch exercise to try solo after the session:
public static int sum(int[] array, int index) {
if (index == array.length) {
return 0; // empty rest adds nothing
}
return array[index] + sum(array, index + 1);
}The base case now returns a VALUE
Why: A void method just stops; a summing method must answer. The sum of nothing is 0 - the identity that makes the additions work.
| call | computes | returns |
|---|---|---|
| sum(arr, 0) | 7 + sum(arr, 1) | 7 + 11 = 18 |
| sum(arr, 1) | 2 + sum(arr, 2) | 2 + 9 = 11 |
| sum(arr, 2) | 9 + sum(arr, 3) | 9 + 0 = 9 |
| sum(arr, 3) | base case | 0 |
Verify: real run on {7, 2, 9} returned 18
Why: Read the table bottom-up to watch the answers assemble during the unwind - the same return trip that printed backwards a moment ago.
Error analysis
Annotate
Walk the callouts on Transfer: a recursive sum. Each one is a place this is easy to get subtly wrong.
Concept
| question | loop | recursion |
|---|---|---|
| print an array forwards | natural | works - as you proved today |
| print it backwards | rewrite the loop header | swap two lines |
| memory used | one frame | one frame per element (101 here) |
| walk folders inside folders | genuinely painful | natural - it IS the shape of the data |
| risk profile | off-by-one bounds | missing base case / not shrinking |
For THIS assignment a loop would be simpler - the assignment chose recursion because the mechanism is the lesson. The payoff arrives with nested structures: folders, trees, and the divide-and-conquer algorithms in your upper-level courses, where recursion is not the alternative but the only sane option.
Trade off
Comparison matrix
From Loop vs recursion: an honest comparison: every row here is a choice with a cost. Fill the recursion column, then say which row you would actually pick and what you give up for it.
| question | loop | recursion |
|---|---|---|
| print an array forwards | natural | works - as you proved today |
| print it backwards | rewrite the loop header | swap two lines |
| memory used | one frame | one frame per element (101 here) |
| walk folders inside folders | genuinely painful | natural - it IS the shape of the data |
| risk profile | off-by-one bounds | missing base case / not shrinking |
Intuition
Folders: a folder holds files and more folders. Printing every file name is printArray with folders as the 'rest' - and the base case is a folder with nothing left inside.
Binary search: check the middle of a sorted array, then search the half that could contain the target. The problem halves every call - base case: one element left.
Sorting: merge sort splits the array in two, recursively sorts each half, and merges. Every one of these is today's skeleton - base case, one piece of work, recurse on something smaller.
Concept
Matching
Match the pairs
From Questions students always ask — match each one to what it actually does. The descriptions have been shuffled.
Why: Why not just use a loop?, Is the for loop in main cheating?, Are two methods named printArray legal? are easy to tell apart while they are sitting next to their descriptions and much harder afterwards, which is what this checks.
Concept
| # | verify before submitting | done |
|---|---|---|
| 1 | printArray calls itself with index + 1; no loop inside it | [ ] |
| 2 | base case is index == array.length, checked first | [ ] |
| 3 | new int[100], filled by for loop with nextInt(100) + 1 | [ ] |
| 4 | output: 100 numbers, spaces, no zeros, nothing over 100 | [ ] |
| 5 | screenshot: code with the recursive method fully visible | [ ] |
| 6 | screenshots: TWO runs with different numbers | [ ] |
Every row maps to a sentence in the assignment. Six check marks = the whole rubric is demonstrated, which is the only currency this assignment pays out in.
Comparison
Comparison matrix
From Final pre-submission checklist: refill the done column from what you know. The rest of the table is as it appeared.
| # | verify before submitting | done |
|---|---|---|
| 1 | printArray calls itself with index + 1; no loop inside it | [ ] |
| 2 | base case is index == array.length, checked first | [ ] |
| 3 | new int[100], filled by for loop with nextInt(100) + 1 | [ ] |
| 4 | output: 100 numbers, spaces, no zeros, nothing over 100 | [ ] |
| 5 | screenshot: code with the recursive method fully visible | [ ] |
| 6 | screenshots: TWO runs with different numbers | [ ] |
Concept
Recursive method — A method that calls itself, directly or indirectly, handling one piece of the problem per call.
Base case — The stopping condition, checked first: for printArray, index == array.length.
Recursive step — The self-call on a strictly smaller problem: printArray(array, index + 1).
Call stack — Java's tower of paused calls - it grows one frame per call on the way down and unwinds as returns cascade back.
Overloading — Two methods sharing a name with different parameter lists - how printArray(array) and printArray(array, index) coexist.
StackOverflowError — The crash when calls pile up without progress toward a base case - diagnosed by the same line repeating 1000+ times in the trace.
Matching
Match the pairs
Match each term to the definition this lesson gave it — not the one you would guess from the word.
Why: These are the working definitions of Base case, Recursive step, Recursive method, Call stack, Overloading as Recursion - Building the printArray() Assignment uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.
Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — Part 1 · What Is Recursion? · Part 2 · Trace It by Hand · Part 3 · Build the Assignment · Part 4 · When It Breaks · Part 5 · Screenshots & Submission · Part 6 · Prove You Own It. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
The assignment is done. A recursive printArray - base case first, one print, then the self-call with index + 1 - displaying a 100-element array filled by a for loop with rand.nextInt(100) + 1. Compiled, run, verified, screenshotted twice.
The mechanism is yours. You traced 4 calls for 3 elements, drew the stack growing and unwinding, and used the unwind on purpose: swapping two lines printed the array backwards - 9 2 7 - with no other change.
The crashes are familiar. No base case: ArrayIndexOutOfBoundsException. No progress toward it: repeated output, then StackOverflowError with the same line stacked 1000+ deep. Two questions diagnose every recursive bug: is there a base case, and does every call move toward it?
Next: the homework's recursive sum and countEven use today's skeleton with different work per call. When they feel easy, you are ready for the recursion that halves problems instead of shrinking them by one - binary search.
Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.