Moving one line to make a countdown count up, the binary number system and repeated division, a recursive method that prints a number in binary, and two CodingBat problems that share the same recursive shape — one over a string and one over an array. Follows Think Java 2e, Chapter 8 (Recursive Methods), Sections 8.5-8.8, pp. 134-140, cross-referenced against Java SE 21 API — Integer.toBinaryString.
Subject: Java · 65 slides · code lesson
Open the interactive version of this deck
Title
Think Java 2e · Chapter 8 · Recursive Methods
Sections 8.5-8.8 · pp. 134-140
Objectives
This lesson follows Think Java 2e, Chapter 8 (Recursive Methods), Sections 8.5-8.8, pp. 134-140. Everything on these slides can be checked against those pages.
1. Explain what changes when the recursive call comes before the work instead of after it.
2. Convert a decimal number to binary by repeated division, and read the remainders in the right order.
3. Write a recursive method that prints a number in binary, and say why the digits come out left to right.
4. Solve a recursive string problem by splitting off the first character and recursing on the rest.
5. Use an index parameter to recurse over an array, and identify the base case.
6. Recognise the shared shape: check the base case, handle the current item, recurse on the rest.
Warm-up
One observation from the previous lesson is about to be exploited.
Discussion prompt
In countdown, does the printing happen before or after the recursive call? And in factorial, does the multiplication happen before or after?
Hint: Look at where the recursive call sits in the body.
Answer:
countdown prints before recursing, so all its output happens on the way down. factorial multiplies after the recursive call returns, so all its work happens on the way up.
That is one line's position, and it decides everything about when things happen. This lesson opens by swapping those two lines in countdown, and the result is a method that counts up.
Concept
The countdown example has three parts: it checks the base case, it displays something, and it makes a recursive call. This lesson is largely about what happens when you reverse the last two.
Figure (svg): Two panels contrasting printing before the recursive call with printing after it, and the opposite output orders they produce
Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 8 (Recursive Methods), Sections 8.5-8.8, pp. 134-140 — Sections 8.5-8.8, printed pages 134-140.
Section
Section 8.5
Concept
The stack diagram is the same as before and the method is still called n times. But now the println happens just before each recursive call returns — so it counts up instead of down.
public static void countup(int n) {
if (n == 0) {
System.out.println("Blastoff!");
} else {
countup(n - 1);
System.out.println(n);
}
}| call | what it does first | what it does after |
|---|---|---|
| countup(3) | calls countup(2) | prints 3 |
| countup(2) | calls countup(1) | prints 2 |
| countup(1) | calls countup(0) | prints 1 |
| countup(0) | prints Blastoff! | returns |
The output is Blastoff!, then 1, 2, 3. Nothing about the recursion changed — the same four calls happen in the same order, and the same four frames appear. Only the moment of printing moved.
Picture it
Draw both and the frames are identical. What differs is which frames have already printed by the time you reach the bottom.
Figure (svg): A stack diagram with main at the top and four countup frames holding n values 3, 2, 1 and 0
At this moment — the deepest point — countdown would already have printed 3, 2, 1 and be about to print Blastoff! countup has printed nothing at all yet. The first thing it prints is Blastoff!, and the numbers follow as the frames unwind from the bottom up.
Worked example
Follow it down first, noticing that nothing is printed, and then watch the output appear as the calls return.
public static void countup(int n) {
if (n == 0) {
System.out.println("Blastoff!");
} else {
countup(n - 1);
System.out.println(n);
}
}| step | what happens | output so far |
|---|---|---|
| 1 | countup(3) calls countup(2) | (nothing) |
| 2 | countup(2) calls countup(1) | (nothing) |
| 3 | countup(1) calls countup(0) | (nothing) |
| 4 | countup(0) prints Blastoff! and returns | Blastoff! |
| 5 | countup(1) resumes and prints 1 | Blastoff! 1 |
| 6 | countup(2) resumes and prints 2 | Blastoff! 1 2 |
| 7 | countup(3) resumes and prints 3 | Blastoff! 1 2 3 |
Go all the way down before anything is printed.
Why: Each call's first statement is the recursive call, so the print is still waiting.
The base case prints first.
Why: It is the deepest frame and the first to finish.
Each frame prints as it resumes.
Why: Control returns to the statement after the recursive call, which is the println — this is Lesson 4a's rule about where a call returns to.
The order is reversed.
Why: The deepest frame prints first and the outermost last, which is the opposite of the order in which the calls started.
Verify: Run both countdown(3) and countup(3) and compare: 3, 2, 1, Blastoff! against Blastoff!, 1, 2, 3.
Why: The lists are exact reverses of one another, from one line's position. That is worth sitting with, because the next section needs precisely this trick to print binary digits in the right order.
Prediction
The recursive call comes first.
public static void countup(int n) {
if (n == 0) {
System.out.println("Blastoff!");
} else {
countup(n - 1);
System.out.println(n);
}
}| frame | prints | when |
|---|---|---|
| countup(0) | Blastoff! | first — deepest |
| countup(1) | 1 | second |
| countup(2) | 2 | last |
Predict first
What is the output?
Correct: Blastoff!, 1, 2
Why: Each call recurses before printing, so nothing appears until the base case is reached — and then the frames print as they return, deepest first. The base case's Blastoff! comes first and the outermost frame's 2 comes last, which is the exact reverse of countdown.
Concept
The calls always start outermost-first. The returns always happen innermost-first. So work placed before the call runs in call order, and work placed after it runs in reverse.
| work placed | runs in | for countup(3) |
|---|---|---|
| before the recursive call | the order calls are made: 3, 2, 1, 0 | counts down |
| after the recursive call | the order calls return: 0, 1, 2, 3 | counts up |
| both | each frame contributes twice | 3, 2, 1, then 1, 2, 3 |
The last row is worth knowing about: a method with a print on both sides of the recursive call produces the sequence down and then back up again. Many algorithms perform computations on the way down, on the way up, or both — and choosing which is a design decision, not an accident.
Trap
The assumption. The first call to start is the first to print.
public static void countup(int n) {
if (n == 0) {
System.out.println("Blastoff!");
} else {
countup(n - 1);
System.out.println(n);
}
}| prediction | reality |
|---|---|
| countup(3) starts first, so 3 prints first | countup(3) starts first and prints LAST |
| Blastoff! prints last | Blastoff! prints first |
| output: 3 2 1 Blastoff! | output: Blastoff! 1 2 3 |
The prediction confuses starting with doing. countup(3) does start first — and its only statement before the recursive call is the recursive call, so it does nothing observable until everything below it has finished.
Read the position of the work relative to the recursive call.
// before the call -> runs on the way DOWN, in call order
System.out.println(n);
countdown(n - 1);
// after the call -> runs on the way UP, in reverse order
countup(n - 1);
System.out.println(n);| question to ask | tells you |
|---|---|
| is the work before or after the recursive call? | down or up |
| which frame finishes first? | the deepest one, always |
| so which output appears first? | the deepest frame's, if the work is after the call |
This single question — before or after? — is the fastest way to read any unfamiliar recursive method. It tells you the order of everything the method does.
Definition probe
Decide from where the work sits relative to the recursive call.
Sort into buckets
Sort each method by when its work happens.
Prediction
Compare against countdown.
Predict first
Does moving the println change the number of frames?
Correct: No — both use 4 countup frames, only the output order differs
Why: The stack diagram is the same as before and the method is still called n times — moving a statement within the body changes neither the number of calls nor the frames they need. Each frame was already holding its own n, so nothing extra has to be remembered.
Socratic
It sounds as though the frame should be finished.
Discussion prompt
When countup(2) calls countup(1), what happens to countup(2)? How does it manage to print something afterwards, and which rule from Chapter 4 is doing the work?
Hint: A call is a detour.
Answer:
countup(2) pauses. Its frame stays on the stack, holding its own n, and control returns to the statement immediately after the call when the inner one finishes — which is the println.
That is exactly Lesson 4a's rule: a method invocation is a detour, and you come back and pick up where you left off. Nothing about recursion changes it.
And this is what the stack is really for: remembering where each paused call has to resume. The frames are not just storage for variables — they are a record of unfinished business, which is why work can happen after a recursive call at all.
Section
Section 8.6
Concept
Computers store only 1s and 0s, because processors and memory are made of billions of tiny on-off switches. Fortunately we can represent any integer as a binary number — and the written representation works exactly like decimal, with powers of two instead of powers of ten.
// decimal 456 - powers of ten
// 4 5 6
// 10^2 10^1 10^0 = 400 + 50 + 6
// binary 10111 - powers of two
// 1 0 1 1 1
// 2^4 2^3 2^2 2^1 2^0 = 16 + 0 + 4 + 2 + 1 = 23| binary | decimal |
|---|---|
| 0 | 0 |
| 1 | 1 |
| 10 | 2 |
| 11 | 3 |
| 100 | 4 |
| 101 | 5 |
| 110 | 6 |
| 111 | 7 |
In decimal there are ten digits and the places are powers of ten. In binary there are two digits and the places are powers of two. Everything else about reading a number is identical.
Picture it
Label each column with its place value, multiply, and add. The method is the one you already use for decimal.
Figure (svg): The binary digits 1, 0, 1, 1, 1 with place values 16, 8, 4, 2 and 1 underneath and the total 23
Only the columns with a 1 contribute. That is what makes binary arithmetic simple for a machine — each place is either counted or not, which is exactly what a switch can represent.
Worked example
To get the digits of a decimal number you can use repeated division by ten. The same method works in binary if you divide by two — and both operators come from Lesson 3b.
23 / 2 is 11 remainder 1
11 / 2 is 5 remainder 1
5 / 2 is 2 remainder 1
2 / 2 is 1 remainder 0
1 / 2 is 0 remainder 1| step | value / 2 | value % 2 | digit produced |
|---|---|---|---|
| 1 | 11 | 1 | rightmost |
| 2 | 5 | 1 | next |
| 3 | 2 | 1 | next |
| 4 | 1 | 0 | next |
| 5 | 0 | 1 | leftmost |
Divide by 2 and keep the remainder.
Why: When you divide by 2 the remainder is the rightmost digit — either 0 or 1.
Divide the result again.
Why: That gives the second rightmost digit, and so on.
Stop when the result reaches 0.
Why: There is nothing left to divide, so there are no more digits.
Read the remainders from bottom to top.
Why: 1, 0, 1, 1, 1 — so 23 in binary is 10111.
Verify: Check it forwards: 16 + 0 + 4 + 2 + 1 is 23.
Why: The step worth noticing is the last one: the remainders come out in reverse order. The first one you compute is the rightmost digit, and the last is the leftmost — which is exactly the problem the count-up pattern solves.
Prediction
Label the places with powers of two.
// 1 1 0 1
// 8 4 2 1| digit | place value | contributes |
|---|---|---|
| 1 | 8 | 8 |
| 1 | 4 | 4 |
| 0 | 2 | 0 |
| 1 | 1 | 1 |
Predict first
What is the decimal value?
Correct: 13
Why: Only the places with a 1 contribute: 8 + 4 + 0 + 1 is 13. The rightmost place is always 1 (two to the power zero) and each place to the left doubles, exactly as each decimal place multiplies by ten.
Concept
This algorithm is Lesson 3b's quotient-and-remainder pattern, applied repeatedly. The same two operators that split inches into feet split a number into its digits.
| operator | in the feet example | here |
|---|---|---|
| / | how many whole feet | the number with its last digit removed |
| % | inches left over | the last digit itself |
| together | 76 = 6 x 12 + 4 | 23 = 11 x 2 + 1 |
Lesson 3b noted that x % 10 yields the rightmost decimal digit. Dividing by 2 instead of 10 gives the rightmost binary digit, for exactly the same reason. Recognising an old pattern in a new base is worth more than memorising the conversion.
Trap
Top to bottom gives the digits backwards.
// remainders, in the order computed:
// 1, 1, 1, 0, 1
// read that way: 11101 = 29 WRONG| read as | value | correct? |
|---|---|---|
| 11101 (top to bottom) | 29 | no |
| 10111 (bottom to top) | 23 | yes |
The error is easy to make and easy to catch: convert back and check. 11101 is 16+8+4+0+1 = 29, which is not the number you started with.
Bottom to top, because the first remainder is the rightmost digit.
// 23 / 2 = 11 r 1 <- rightmost digit
// 11 / 2 = 5 r 1
// 5 / 2 = 2 r 1
// 2 / 2 = 1 r 0
// 1 / 2 = 0 r 1 <- leftmost digit
//
// reading up: 1 0 1 1 1 = 10111| computed | position in the answer |
|---|---|
| first remainder | rightmost digit |
| last remainder | leftmost digit |
| so read | in reverse of the order computed |
Always check by converting back. It costs ten seconds and catches this error every time — which is the same habit as Lesson 3b's quotient times divisor plus remainder equals the original.
Prediction
The remainder after dividing by two.
10 % 2| expression | value | meaning |
|---|---|---|
| 10 / 2 | 5 | the rest of the number |
| 10 % 2 | 0 | the rightmost binary digit |
Predict first
What is 10 % 2, and what does it tell you?
Correct: 0 — the rightmost binary digit of 10 is 0, so 10 is even
Why: Dividing by two leaves remainder 0, which is the rightmost binary digit. That is also the standard even-number test from Lesson 5a — and now you can see why the two are the same fact: a number is even exactly when its last binary digit is 0.
Ranking
Repeated division, then read the remainders.
Put in order
Why: Each division produces the next digit from the right and reduces the value, and the process stops when the value reaches 0. The remainders come out 0, 1, 1 — and reading them in reverse gives 110, which is 4 + 2 + 0, or 6.
Real world
Decimal is more convenient for people. The machine's choice is about physics.
Discussion prompt
Processors and memory are made of billions of tiny on-off switches. Why does that make base two the natural choice, and what would base ten require of the hardware?
Hint: How many distinguishable states does a switch have?
Answer:
A switch has two reliably distinguishable states: on and off. Base two needs exactly two symbols, so one switch stores one digit with no ambiguity.
Base ten would need each component to distinguish ten different voltage levels reliably, in the presence of noise, heat and manufacturing variation. It has been tried and it is far harder to make work.
So binary is not a design preference — it is the number system that matches the hardware's physics. All types of data, whether integer, floating-point, text, audio or video, are represented by 1s and 0s for the same reason.
Section
Section 8.7
Concept
To display a number in binary, combine the repeated-division algorithm with the count-up pattern from Section 8.5. The recursion divides going down; the printing happens coming back.
public static void displayBinary(int value) {
if (value > 0) {
displayBinary(value / 2);
System.out.print(value % 2);
}
}| call | value | recurses on | prints on return |
|---|---|---|---|
| displayBinary(23) | 23 | 11 | 23 % 2 = 1 |
| displayBinary(11) | 11 | 5 | 11 % 2 = 1 |
| displayBinary(5) | 5 | 2 | 5 % 2 = 1 |
| displayBinary(2) | 2 | 1 | 2 % 2 = 0 |
| displayBinary(1) | 1 | 0 | 1 % 2 = 1 |
| displayBinary(0) | 0 | — | nothing — base case |
If value is 0, displayBinary does nothing — that is the base case. Otherwise it divides by 2, calls itself, and when the recursive call returns displays one digit.
Picture it
The stack holds the divisions; the printing happens as it unwinds, so the last division printed is the first one made.
Figure (svg): A stack diagram for displayBinary called with 23, showing frames for values 23, 11, 5, 2, 1 and 0
The leftmost digit is near the bottom of the stack, so it gets displayed first. The rightmost digit, near the top, gets displayed last. That is exactly the reversal the count-up pattern provides — and it is why the digits come out in the right order without any array or string to hold them.
Worked example
Six calls going down, five digits coming back. Watch which frame prints which digit.
public static void displayBinary(int value) {
if (value > 0) {
displayBinary(value / 2);
System.out.print(value % 2);
}
}| frame | value | value % 2 | printed | output so far |
|---|---|---|---|---|
| deepest | 0 | — | nothing | |
| 5th | 1 | 1 | 1 | 1 |
| 4th | 2 | 0 | 0 | 10 |
| 3rd | 5 | 1 | 1 | 101 |
| 2nd | 11 | 1 | 1 | 1011 |
| 1st | 23 | 1 | 1 | 10111 |
Descend, halving each time.
Why: 23, 11, 5, 2, 1, 0 — the same sequence as the repeated division on paper.
The base case prints nothing.
Why: value > 0 is false when value is 0, so the method does nothing and returns.
Each frame prints its own remainder as it resumes.
Why: The deepest non-zero frame prints first, and it holds the leftmost digit.
Read the output.
Why: 10111 — the digits appear left to right, in the order a person reads them.
Verify: Run displayBinary(23); System.out.println(); and expect 10111.
Why: Then check it against the paper conversion: the remainders were 1, 1, 1, 0, 1 read bottom to top, giving 10111. The recursion did the reversing for you, which is precisely the work the count-up pattern was introduced to do.
Note the println() after the call: displayBinary uses print, so it never ends the line itself.
Prediction
Divide down, print up.
public static void displayBinary(int value) {
if (value > 0) {
displayBinary(value / 2);
System.out.print(value % 2);
}
}| value | value % 2 | printed when |
|---|---|---|
| 5 | 1 | last |
| 2 | 0 | second |
| 1 | 1 | first |
| 0 | — | nothing |
Predict first
What is the output?
Correct: 101
Why: The calls descend 5, 2, 1, 0 and print on the way back, so the frame holding 1 prints first, then the one holding 2 prints 0, then the one holding 5 prints 1 — giving 101, which is 4 + 0 + 1, or 5.
Concept
Swapping the two lines would give a method that compiles, runs and prints the digits backwards. The position of the recursive call is doing all the work.
// correct: digits left to right
displayBinary(value / 2);
System.out.print(value % 2);
// swapped: digits right to left
System.out.print(value % 2);
displayBinary(value / 2);| version | for 23 | why |
|---|---|---|
| print after the call | 10111 | deepest frame prints first — leftmost digit |
| print before the call | 11101 | outermost frame prints first — rightmost digit |
The second version produces the digits in the order the divisions computed them, which is backwards. Both are one-line methods and the difference between them is entirely which side of the recursive call the print sits on — the question from the first idea of this lesson.
Trap
Printing in the base case adds a leading zero.
public static void displayBinary(int value) {
if (value == 0) {
System.out.print(0); // wrong: prints a leading 0
} else {
displayBinary(value / 2);
System.out.print(value % 2);
}
}| input | expected | actual |
|---|---|---|
| 23 | 10111 | 010111 |
| 1 | 1 | 01 |
The base case is reached once, at the very bottom, and it is the first thing to print — so its output lands at the far left of the number.
The base case does nothing at all.
public static void displayBinary(int value) {
if (value > 0) {
displayBinary(value / 2);
System.out.print(value % 2);
}
}| input | output | note |
|---|---|---|
| 23 | 10111 | correct |
| 1 | 1 | correct |
| 0 | (nothing) | an edge case worth noticing |
Writing the condition as if (value > 0) with no else makes the base case do nothing, which is exactly right here. Note the last row though: displayBinary(0) prints nothing at all rather than a zero, so the method works for positive integers, as the book says.
Prediction
Swap the two lines.
System.out.print(value % 2);
displayBinary(value / 2);| version | order of printing |
|---|---|
| print after the call | deepest frame first — left to right |
| print before the call | outermost frame first — right to left |
Predict first
For an input of 5, what would the swapped version print?
Correct: 101 reversed, which is 101
Why: For 5 the digits are 1, 0, 1 — a palindrome — so reversing them gives the same string, and this particular input hides the bug entirely. That is exactly why it is a bad test case: try 23, where the correct answer 10111 becomes 11101 and the mistake is obvious.
Fill the middle
Print the digits left to right.
Fill in the blanks
public static void displayBinary(int value) >} 0) /} 2);
System.out.print(value % 2);
}
}
Why: value > 0 makes the base case do nothing, so no leading zero appears. Division by 2 removes the last binary digit and moves the recursion toward the base case, and the remainder IS that digit — printed after the call so it comes out in reading order.
Explain it to yourself
The repeated division is easy to write as a loop. Something else is not.
Discussion prompt
You could compute the binary digits with a while loop dividing by 2. What would be awkward about printing them, and what does the recursion give you for free?
Hint: In what order does the loop produce the digits?
Answer:
A loop produces the digits rightmost first, which is the reverse of the order you want to print them. So the loop version has to store them — in a string or an array — and then print them backwards.
The recursion gives that reversal for free, because the stack is already remembering each unfinished call and unwinds in the opposite order. The frames are doing the job the array would have done.
That is a genuinely good reason to choose recursion: when you need work done in reverse order, the call stack already provides it. It is the same reason noX on the next slides can build a string without ever reversing anything.
Section
Section 8.8
Concept
CodingBat's noX problem: given a string, compute recursively a new string with all the 'x' characters removed. When solving recursive problems it helps to think about the base case first.
// noX("xaxb") -> "ab"
// noX("abc") -> "abc"
// noX("xx") -> ""
if (str.length() == 0) {
return ""; // the base case
}
char first = str.charAt(0); // split into
String rest = str.substring(1); // first and rest| step | for "xaxb" |
|---|---|
| is it empty? | no |
| first | 'x' |
| rest | "axb" |
| recurse on rest | noX("axb") |
The base case is the easiest version of the problem: for noX it is the empty string, which has no x's to remove, so the answer is the empty string. Then split the string into two parts — the first letter and the rest — using charAt and substring from Lesson 6b.
Notation
To solve a problem recursively you need to think of a simpler instance of the same problem. For noX that is removing the x's from a shorter string.
Annotate
substring(1) — everything from index 1 to the end, which is Lesson 6b's one-argument form. That string is strictly shorter, which is what guarantees progress toward the base case.noX(rest) correctly removes every x from the shorter string.first is an x we are done — just return what the recursion gave us. Otherwise we have to put first back on the front.first + recurse is concatenation, a char joined to a String, which produces a String (Lesson 2b).Four moves, and only the third involves any faith. The base case and the split can both be checked by reading.
Worked example
Follow it down to the empty string, then watch the result being rebuilt on the way up.
public static String noX(String str) {
if (str.length() == 0) {
return "";
}
char first = str.charAt(0);
String rest = str.substring(1);
String recurse = noX(rest);
if (first == 'x') {
return recurse;
} else {
return first + recurse;
}
}| call | first | rest | recurse returns | this call returns |
|---|---|---|---|---|
| noX("xab") | 'x' | "ab" | "ab" | "ab" — x dropped |
| noX("ab") | 'a' | "b" | "b" | "ab" — a kept |
| noX("b") | 'b' | "" | "" | "b" — b kept |
| noX("") | — | — | — | "" — base case |
Descend by removing one character at a time.
Why: "xab", then "ab", then "b", then "" — each strictly shorter than the last.
The base case returns the empty string.
Why: Nothing to remove, nothing to return.
Rebuild on the way up.
Why: noX("b") gets back "" and puts 'b' on the front, giving "b".
Drop the x when you meet it.
Why: noX("xab") gets back "ab" and, since its first character is an x, simply returns it unchanged.
Verify: Expect noX("xaxb") to be "ab", noX("abc") to be "abc", and noX("xx") to be "".
Why: The third case is worth checking deliberately: every character is dropped, so the recursion rebuilds nothing and the empty string comes all the way back up. A method that works on "abc" and fails on "xx" has usually got the drop-versus-keep decision backwards.
Prediction
Trace down, then rebuild.
public static String noX(String str) {
if (str.length() == 0) {
return "";
}
char first = str.charAt(0);
String rest = str.substring(1);
String recurse = noX(rest);
if (first == 'x') {
return recurse;
} else {
return first + recurse;
}
}| call | first | action |
|---|---|---|
| noX("xxa") | 'x' | drop it |
| noX("xa") | 'x' | drop it |
| noX("a") | 'a' | keep it |
| noX("") | — | return "" |
Predict first
What is the result?
Correct: "a"
Why: Both x's are dropped and the a is kept, so the result is the single-character string "a". Each call makes one decision about one character and trusts the recursion for the rest — which is the leap of faith doing its work.
Concept
Notice that noX never reverses anything, even though it takes the string apart from the front. The stack handles the ordering, exactly as it did for displayBinary.
Figure (svg): A trace strip showing the empty string being returned and each character being added back on the front as the calls return
Each character is put back on the front of the result, and the characters come back in reverse order — so the two reversals cancel and the string comes out the right way round. That is the same mechanism as the binary digits, in a different costume.
Trap
The recursive call is not on a shorter string.
public static String noX(String str) {
if (str.length() == 0) {
return "";
}
char first = str.charAt(0);
String recurse = noX(str); // str, not the rest
...
}| call | argument | shorter? |
|---|---|---|
| noX("abc") | "abc" | no |
| noX("abc") | "abc" | no |
| ... | ... | StackOverflowError |
The base case is correct and never reached, because the argument never shrinks. This is the second of the three checks from Lesson 8a failing on its own.
Recurse on rest, which is strictly shorter by one character.
String rest = str.substring(1);
String recurse = noX(rest);| call | argument | length |
|---|---|---|
| noX("abc") | "abc" | 3 |
| noX("bc") | "bc" | 2 |
| noX("c") | "c" | 1 |
| noX("") | "" | 0 — base case |
substring(1) removes exactly one character, so the length decreases by one every call and the base case at length 0 is guaranteed to be reached. Check that the argument shrinks — it is the cheapest of the three checks and catches the most common mistake.
Ranking
The shape that solves nearly every problem of this kind.
Put in order
Why: The base case must be checked first, or charAt(0) throws on an empty string. Then split, then recurse, then combine — and the combination is the only step where the decision about this particular problem actually lives.
Fill the middle
Drop the x, keep everything else.
Fill in the blanks
String rest = str.substring(1);
String recurse = noX(rest);
if (first == 'x') recurse};
} else ___
Why: substring(1) is everything from index 1 onwards — the string without its first character — which is what makes each call shorter. When the first character is an x it is simply not added back, so the method returns the recursive result unchanged.
Edge cases
Order matters more than usual here.
Discussion prompt
Suppose the base case check were moved to the bottom of the method, after the split. What would go wrong, and on which input?
Hint: What does charAt(0) do to an empty string?
Answer:
str.charAt(0) on an empty string throws StringIndexOutOfBoundsException — there is no character at index 0. So the method would crash on the empty string, which is precisely the input the base case exists to handle.
And every recursive call eventually reaches the empty string, so it would crash on every input, not just on an empty one.
This generalises: the base case must be checked before any operation that assumes the problem is non-trivial. Splitting, indexing and taking a first element all make that assumption, which is why the base case always comes first.
Section
Section 8.8, continued
Concept
CodingBat's array11: given an array of ints, compute recursively the number of times the value 11 appears. An array cannot be shortened the way a string can, so this problem uses a different convention: pass the index as an argument.
// array11([1, 2, 11], 0) -> 1
// array11([11, 11], 0) -> 2
// array11([1, 2, 3, 4], 0) -> 0
if (index >= nums.length) {
return 0; // base case: past the end
}| the string version | the array version |
|---|---|
| shorten with substring(1) | advance the index by 1 |
| base case: length is 0 | base case: index is past the end |
| look at charAt(0) | look at nums[index] |
| recurse on rest | recurse with index + 1 |
The base case is when we have reached the end of the array — at that point we know there are no more 11s, so the answer is 0. The index moving forward is what makes progress toward it.
Picture it
Nothing about the array changes. What shrinks is the amount of it still to be looked at, and the index records where that starts.
Figure (svg): An array of four elements with the elements before index 2 dimmed and the element at index 2 highlighted as the current item
Each call handles one element — the one at index — and trusts the recursion for everything after it. The array itself is passed unchanged every time, which is cheap because what is passed is a reference (Lesson 7a).
Worked example
Similar to noX, we look at only one integer per method call. Follow it to the end of the array and count back.
public static int array11(int[] nums, int index) {
if (index >= nums.length) {
return 0;
}
int recurse = array11(nums, index + 1);
if (nums[index] == 11) {
return recurse + 1;
} else {
return recurse;
}
}| call | nums[index] | recurse returns | this call returns |
|---|---|---|---|
| array11(a, 0) | 1 | 1 | 1 — not an 11 |
| array11(a, 1) | 2 | 1 | 1 — not an 11 |
| array11(a, 2) | 11 | 0 | 1 — an 11, so add one |
| array11(a, 3) | past the end | — | 0 — base case |
Check the base case first.
Why: index >= nums.length means we have run off the end, and there are no more 11s to find.
Recurse on the rest of the array.
Why: array11(nums, index + 1) counts the 11s from the next position onwards.
Look at the current element.
Why: If it is 11, add one to whatever the recursion found; otherwise return that count unchanged.
Take the leap of faith.
Why: Assume array11(nums, index + 1) correctly counts the rest, and ask only whether this element should add one. It should, exactly when it is 11.
Verify: Expect array11([1, 2, 11], 0) to be 1, array11([11, 11], 0) to be 2, and array11([1, 2, 3, 4], 0) to be 0.
Why: The middle case is the one that checks the addition: two 11s must give 2, which only happens if each call adds its own contribution to the recursive result rather than replacing it.
Prediction
Count on the way back.
public static int array11(int[] nums, int index) {
if (index >= nums.length) {
return 0;
}
int recurse = array11(nums, index + 1);
if (nums[index] == 11) {
return recurse + 1;
} else {
return recurse;
}
}| index | nums[index] | contributes |
|---|---|---|
| 0 | 11 | +1 |
| 1 | 5 | +0 |
| 2 | 11 | +1 |
| 3 | past the end | 0 |
Predict first
For the array [11, 5, 11] starting at index 0, what is returned?
Correct: 2
Why: Two elements equal 11, and each of those calls adds one to the count returned from the rest of the array. The base case returns 0 and each frame adds its own contribution as the stack unwinds, giving 0 + 1 + 0 + 1 = 2.
Concept
Both CodingBat problems have the same recursive idea: check the base case, look at the current item, and recursively handle the rest. Only the details of each step differ.
| step | noX (String) | array11 (int[]) |
|---|---|---|
| base case | the string is empty | the index is past the end |
| base case returns | "" | 0 |
| the current item | str.charAt(0) | nums[index] |
| the rest | str.substring(1) | index + 1 |
| combine | add the char, or do not | add one, or do not |
Reading the table down each column gives a complete method. Reading it across shows that they are the same method applied to two different structures — which is what it means to have learned a pattern rather than two solutions.
Trap
The recursion is called and its answer thrown away.
int recurse = array11(nums, index + 1);
if (nums[index] == 11) {
return 1; // ignores recurse
} else {
return 0; // ignores it too
}| array | correct answer | this version |
|---|---|---|
| [11, 11] | 2 | 1 |
| [1, 11] | 1 | 0 |
| [11, 1] | 1 | 1 — accidentally right |
This counts only the element at the starting index. The recursive call still runs — and its result is computed and discarded, exactly like the ignored return value in Lesson 4b.
Combine your element's contribution with the recursive result.
int recurse = array11(nums, index + 1);
if (nums[index] == 11) {
return recurse + 1;
} else {
return recurse;
}| array | each call contributes | total |
|---|---|---|
| [11, 11] | 1 + 1 + 0 | 2 |
| [1, 11] | 0 + 1 + 0 | 1 |
| [1, 2, 3] | 0 + 0 + 0 + 0 | 0 |
The word combine is doing real work in the pattern. A recursive method that does not use its recursive result is not recursive in any useful sense — it is an expensive way to look at one element.
Definition probe
The same three roles as every recursive method.
Sort into buckets
Sort each line of array11.
Matching
The same pattern, two structures.
Match the pairs
Why: Each pair plays the same role: the base-case test, the current item, the way of naming the rest, and the base case's value. The string version shortens the data; the array version advances a marker — but the shape of the method is identical, which is what makes it a pattern.
Counterexample
The string version passes a shorter string. Ask why the array version does not.
Discussion prompt
noX calls itself with a shorter string. Why does array11 pass an index instead of a shorter array — what would the shortening version cost?
Hint: How would you make an array one element shorter?
Answer:
There is no cheap way to shorten an array. You would have to create a new array with Arrays.copyOfRange and copy every remaining element — so a call on an n-element array would copy n-1 elements, and the whole recursion would copy roughly n squared elements.
Passing an index copies nothing. The array reference is passed unchanged — which is cheap precisely because it is a reference (Lesson 7a) — and only the index moves.
Strings have the same problem, incidentally: substring also creates a new string. For short strings it does not matter, and the index convention would work there too. The general lesson is that the rest of the data can be named either by making it or by pointing at it, and pointing is usually cheaper.
Comparison
Fill the blanks. Every column is the same four questions with different answers.
Comparison matrix
| displayBinary | noX | array11 | |
|---|---|---|---|
| base case | value is 0 | the string is empty | index is past the end |
| what shrinks | value, halved | the string, by one character | the part still to search — the index advances |
| work happens | on the way up | on the way up | on the way up |
| combines by | printing a digit | adding a character, or not | adding one, or not |
All three do their work on the way up, which is why all three needed the count-up pattern from Section 8.5. That one observation about line order carries the whole lesson.
Pattern
Check the base case, look at the current item, recursively handle the rest. Both CodingBat problems are this, and so is almost every recursive problem over a sequence.
ReturnType solve(Data data, position p) {
if (pastTheEnd(p)) { // 1. base case
return emptyAnswer; // "" for strings, 0 for counts
}
Item current = itemAt(data, p); // 2. the current item
ReturnType rest = solve(data, next(p)); // 3. recurse on the rest
return combine(current, rest); // 4. combine
}| for a string | for an array |
|---|---|
| base case: length is 0 | base case: index >= length |
| current: charAt(0) | current: nums[index] |
| rest: substring(1) | rest: index + 1 |
| empty answer: "" | empty answer: 0 |
Check
Work it out before you click.
public static void countup(int n) {
if (n == 0) {
System.out.println("Blastoff!");
} else {
countup(n - 1);
System.out.println(n);
}
}| frame | prints | order |
|---|---|---|
| n = 0 | Blastoff! | first |
| n = 1 | 1 | second |
| n = 2 | 2 | third |
Check your understanding
What does countup(2) display?
Answer: A
Why: The recursive call comes before the println, so nothing is printed until the base case is reached; then each frame prints as it returns, deepest first. The base case prints Blastoff! and the numbers follow in increasing order.
Check
Work it out before you click.
6 / 2 is 3 remainder 0
3 / 2 is 1 remainder 1
1 / 2 is 0 remainder 1| remainder | position |
|---|---|
| 0 (first computed) | rightmost |
| 1 | middle |
| 1 (last computed) | leftmost |
Check your understanding
What is 6 in binary?
Answer: A
Why: The remainders are computed rightmost-first, so they must be read bottom to top: 1, 1, 0 gives 110. Checking forwards confirms it: 4 + 2 + 0 is 6.
Check
Work it out before you click.
public static int count(int[] nums, int index) {
if (index >= nums.length) {
return 0;
}
int recurse = count(nums, index + 1);
if (nums[index] == 7) {
return recurse + 1;
}
return recurse;
}| index | nums[index] | contributes |
|---|---|---|
| 0 | 7 | +1 |
| 1 | 7 | +1 |
| 2 | 3 | +0 |
Check your understanding
For the array [7, 7, 3] starting at index 0, what is returned?
Answer: A
Why: Two elements equal 7, and each of those calls adds one to the count returned from the rest of the array, so the total is 2. The base case contributes 0, and each frame adds its own contribution as the stack unwinds.
Real world
Two methods in this lesson needed their results in the opposite order from the one they were computed in, and neither used an array to reverse anything.
Discussion prompt
displayBinary computes digits rightmost-first and prints them leftmost-first. noX takes characters off the front and puts them back on the front. Where is the reversal actually happening — and what would you need without recursion?
Hint: What does the stack unwind in?
Answer:
The reversal is in the stack itself. Calls are made outermost-first and return innermost-first, so any work placed after a recursive call happens in reverse order automatically.
Without recursion you would need an explicit data structure to hold the intermediate results — a string, an array, or a stack of your own — and then a second pass to read it backwards.
This is one of the genuinely good reasons to reach for recursion. When a problem naturally produces answers in the wrong order, the call stack is already the data structure you would otherwise have had to build. It is also why undo systems, expression evaluators and backtracking searches are so often written recursively.
Commit first
Commit to an answer and to your confidence.
Predict first
In displayBinary, what would happen if the print came before the recursive call?
Correct: The digits would be printed in reverse order
Why: Work placed before the recursive call runs on the way down, in the order the calls are made — and the divisions produce digits rightmost-first. So displayBinary(23) would print 11101 rather than 10111. The recursion would still terminate correctly and the base case would still do nothing; only the ORDER changes. That is the whole point of Section 8.5, and it is why one line's position is worth this much attention.
Explain it
Two minutes, drawing as you go.
Discussion prompt
Explain to a classmate why displayBinary(23) prints 10111 and not 11101, given that the first division produces the rightmost digit. Draw the stack, and point at the frame that prints first.
Hint: Which frame is deepest, and which one finishes first?
Answer:
Draw six frames going down: 23, 11, 5, 2, 1, 0. The divisions happen on the way down, so the frame holding 23 was made first — but its print statement is after the recursive call, so it has not run yet.
The frame holding 0 does nothing and returns. Then the frame holding 1 prints its remainder, 1 — and that is the leftmost digit of the answer. Then 2 prints 0, then 5 prints 1, and so on up to 23 printing the last digit. The deepest frame prints first, so the digits come out in reading order.
If the drawing does not have arrows showing the returns coming back up, add them — the whole explanation is about the return order rather than the call order.
Exit ticket
One question before you close the deck.
Predict first
What decides whether a recursive method's work happens on the way down or on the way up?
Correct: Whether the work is written before or after the recursive call
Why: Calls are made outermost-first and return innermost-first, so a statement placed before the recursive call runs in call order — on the way down — while one placed after it waits for that call to return and runs in the reverse order. Moving a single println between those two positions is the only difference between countdown and countup, and it is what makes displayBinary print its digits in reading order rather than backwards.
Connect it up
One page, from memory.
Draw it
Draw the stack for displayBinary(11) — one frame per call, labelled with its value — and beside each frame write the digit it prints and the order in which it prints. Underneath, do the paper conversion of 11 to binary by repeated division and confirm the two agree. Then write the four-step recursive pattern (base case, current item, recurse on the rest, combine) and fill it in for both noX and array11.
Recap
Four sections that turn recursion from a curiosity into a technique — and one observation about line order that explains all four.
| if you remember one thing | it is this |
|---|---|
| about order | before the call is down, after the call is up |
| about sequences | base case, current item, rest, combine |
| about arrays | advance an index rather than copying a shorter array |
substring(1); for an array, pass index + 1.Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.