Counting Up, Binary Numbers, and Recursive Practice

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

What this lesson covers

The lesson, slide by slide

1. Counting Up, Binary Numbers, and Recursive Practice

Title

Think Java 2e · Chapter 8 · Recursive Methods

Sections 8.5-8.8 · pp. 134-140

2. What you will be able to do

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.

3. Retrieve before you read

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.

4. Before or after the recursive call

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.

5. Counting up recursively

Section

Section 8.5

6. Move the print after the recursive call

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);
    }
}
callwhat it does firstwhat 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.

7. The same stack, the opposite output

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.

8. Tracing countup(3)

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);
    }
}
stepwhat happensoutput so far
1countup(3) calls countup(2)(nothing)
2countup(2) calls countup(1)(nothing)
3countup(1) calls countup(0)(nothing)
4countup(0) prints Blastoff! and returnsBlastoff!
5countup(1) resumes and prints 1Blastoff! 1
6countup(2) resumes and prints 2Blastoff! 1 2
7countup(3) resumes and prints 3Blastoff! 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.

9. What does countup(2) print?

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);
    }
}
frameprintswhen
countup(0)Blastoff!first — deepest
countup(1)1second
countup(2)2last

Predict first

What is the output?

  • Blastoff!, 1, 2
  • 2, 1, Blastoff!
  • Blastoff!, 2, 1
  • 1, 2, Blastoff!

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.

10. Why the reversal happens

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 placedruns infor countup(3)
before the recursive callthe order calls are made: 3, 2, 1, 0counts down
after the recursive callthe order calls return: 0, 1, 2, 3counts up
botheach frame contributes twice3, 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.

11. Expecting the output order to match the call order

Trap

The 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);
    }
}
predictionreality
countup(3) starts first, so 3 prints firstcountup(3) starts first and prints LAST
Blastoff! prints lastBlastoff! 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.

The fix

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 asktells 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.

12. Down or up?

Definition probe

Decide from where the work sits relative to the recursive call.

Sort into buckets

Sort each method by when its work happens.

on the way down
println(n); then countdown(n - 1);
on the way up
countup(n - 1); then println(n);; return n * factorial(n - 1);; displayBinary(value / 2); then print(value % 2);
down
The work comes before the recursive call, so it happens as the calls are being made — in the order the frames were created.
up
The work comes after the recursive call, so it waits until that call returns and happens as the frames unwind — deepest first.

13. How many frames does countup(3) use?

Prediction

Compare against countdown.

Predict first

Does moving the println change the number of frames?

  • No — both use 4 countup frames, only the output order differs
  • Yes — countup uses fewer, because it prints later
  • Yes — countup uses more, because it must remember what to print
  • It depends on the value of n

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.

14. Why can a frame do work after its recursive call?

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.

15. The binary number system

Section

Section 8.6

16. Place values, in base two

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
binarydecimal
00
11
102
113
1004
1015
1106
1117

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.

17. Reading 10111

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.

18. Converting 23 to binary by repeated division

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
stepvalue / 2value % 2digit produced
1111rightmost
251next
321next
410next
501leftmost

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.

19. What is 1101 in decimal?

Prediction

Label the places with powers of two.

//  1    1    0    1
//  8    4    2    1
digitplace valuecontributes
188
144
020
111

Predict first

What is the decimal value?

  • 13
  • 11
  • 14
  • 1101

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.

20. The two operators, doing exactly their jobs

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.

operatorin the feet examplehere
/how many whole feetthe number with its last digit removed
%inches left overthe last digit itself
together76 = 6 x 12 + 423 = 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.

21. Reading the remainders in the order you computed them

Trap

The 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 asvaluecorrect?
11101 (top to bottom)29no
10111 (bottom to top)23yes

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.

The fix

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
computedposition in the answer
first remainderrightmost digit
last remainderleftmost digit
so readin 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.

22. What is the rightmost binary digit of 10?

Prediction

The remainder after dividing by two.

10 % 2
expressionvaluemeaning
10 / 25the rest of the number
10 % 20the rightmost binary digit

Predict first

What is 10 % 2, and what does it tell you?

  • 0 — the rightmost binary digit of 10 is 0, so 10 is even
  • 1 — the rightmost digit is 1
  • 5 — the quotient
  • 2 — the divisor

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.

23. Order the steps of converting 6 to binary

Ranking

Repeated division, then read the remainders.

Put in order

  1. 6 / 2 is 3 remainder 0
  2. 3 / 2 is 1 remainder 1
  3. 1 / 2 is 0 remainder 1
  4. read the remainders bottom to top: 110

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.

24. Why does the machine use binary at all?

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.

25. The recursive binary method

Section

Section 8.7

26. Divide on the way down, print on the way up

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);
    }
}
callvaluerecurses onprints on return
displayBinary(23)231123 % 2 = 1
displayBinary(11)11511 % 2 = 1
displayBinary(5)525 % 2 = 1
displayBinary(2)212 % 2 = 0
displayBinary(1)101 % 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.

27. The leftmost digit is at the bottom of the stack

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.

28. Tracing displayBinary(23)

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);
    }
}
framevaluevalue % 2printedoutput so far
deepest0—nothing
5th1111
4th20010
3rd511101
2nd11111011
1st231110111

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.

29. What does displayBinary(5) print?

Prediction

Divide down, print up.

public static void displayBinary(int value) {
    if (value > 0) {
        displayBinary(value / 2);
        System.out.print(value % 2);
    }
}
valuevalue % 2printed when
51last
20second
11first
0—nothing

Predict first

What is the output?

  • 101
  • 011
  • 110
  • 5

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.

30. Why print after, and not before

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);
versionfor 23why
print after the call10111deepest frame prints first — leftmost digit
print before the call11101outermost 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.

31. A base case of value == 0 that prints

Trap

The 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);
    }
}
inputexpectedactual
2310111010111
1101

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 fix

The base case does nothing at all.

public static void displayBinary(int value) {
    if (value > 0) {
        displayBinary(value / 2);
        System.out.print(value % 2);
    }
}
inputoutputnote
2310111correct
11correct
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.

32. What if the print came first?

Prediction

Swap the two lines.

System.out.print(value % 2);
displayBinary(value / 2);
versionorder of printing
print after the calldeepest frame first — left to right
print before the calloutermost frame first — right to left

Predict first

For an input of 5, what would the swapped version print?

  • 101 reversed, which is 101
  • 110
  • 011
  • 101

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.

33. Complete displayBinary

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.

34. Why does this need recursion rather than a loop?

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.

35. Recursion over a string

Section

Section 8.8

36. Split off the first character, recurse on the rest

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
stepfor "xaxb"
is it empty?no
first'x'
rest"axb"
recurse on restnoX("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.

37. The whole method, built in four moves

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

  • The base case comes first, and it is the easiest version: an empty string has no x's, so the answer is the empty string.
  • Splitting uses 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.
  • The recursive call handles the rest, and this is where the leap of faith goes: assume noX(rest) correctly removes every x from the shorter string.
  • The combination is one decision. If 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.

38. Tracing noX on "xab"

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;
    }
}
callfirstrestrecurse returnsthis 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.

39. What does noX("xxa") return?

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;
    }
}
callfirstaction
noX("xxa")'x'drop it
noX("xa")'x'drop it
noX("a")'a'keep it
noX("")—return ""

Predict first

What is the result?

  • "a"
  • "xxa"
  • ""
  • "ax"

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.

40. Building a string on the way up

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.

41. Recursing on the wrong part

Trap

The 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
    ...
}
callargumentshorter?
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.

The fix

Recurse on rest, which is strictly shorter by one character.

String rest = str.substring(1);
String recurse = noX(rest);
callargumentlength
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.

42. Order the steps of a recursive string method

Ranking

The shape that solves nearly every problem of this kind.

Put in order

  1. check the base case: is the string empty?
  2. split into the first character and the rest
  3. recursively solve the problem for the rest
  4. combine the first character with the recursive result

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.

43. Complete noX

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.

44. What happens if the base case is checked last?

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.

45. Recursion over an array

Section

Section 8.8, continued

46. Pass the index as an argument

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 versionthe array version
shorten with substring(1)advance the index by 1
base case: length is 0base case: index is past the end
look at charAt(0)look at nums[index]
recurse on restrecurse 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.

47. The index is the shrinking part

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).

48. Tracing array11

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;
    }
}
callnums[index]recurse returnsthis call returns
array11(a, 0)111 — not an 11
array11(a, 1)211 — not an 11
array11(a, 2)1101 — 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.

49. What does array11 return?

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;
    }
}
indexnums[index]contributes
011+1
15+0
211+1
3past the end0

Predict first

For the array [11, 5, 11] starting at index 0, what is returned?

  • 2
  • 1
  • 3
  • 0

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.

50. One shape, two problems

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.

stepnoX (String)array11 (int[])
base casethe string is emptythe index is past the end
base case returns""0
the current itemstr.charAt(0)nums[index]
the reststr.substring(1)index + 1
combineadd the char, or do notadd 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.

51. Forgetting to use the recursive result

Trap

The 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
}
arraycorrect answerthis version
[11, 11]21
[1, 11]10
[11, 1]11 — 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.

The fix

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;
}
arrayeach call contributestotal
[11, 11]1 + 1 + 02
[1, 11]0 + 1 + 01
[1, 2, 3]0 + 0 + 0 + 00

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.

52. Which part plays which role?

Definition probe

The same three roles as every recursive method.

Sort into buckets

Sort each line of array11.

the base case
if (index >= nums.length) return 0;
the progress
array11(nums, index + 1); index + 1 as the argument
the combination
return recurse + 1;
base
Resolves directly with no recursive call — running past the end means there is nothing left to count.
prog
The recursive call, and the argument that makes it strictly closer to the base case. The index advancing is what guarantees the end is reached.
comb
Builds this call's answer from the recursive result — the only step where the leap of faith is used.

53. Match the string version to the array version

Matching

The same pattern, two structures.

Match the pairs

  • a. str.length() == 0
  • b. str.charAt(0)
  • c. str.substring(1)
  • d. return ""
  • r1. index >= nums.length
  • r2. nums[index]
  • r3. index + 1
  • r4. return 0

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.

54. Why not shorten the array instead?

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.

55. The recursive pattern, across three problems

Comparison

Fill the blanks. Every column is the same four questions with different answers.

Comparison matrix

displayBinarynoXarray11
base casevalue is 0the string is emptyindex is past the end
what shrinksvalue, halvedthe string, by one characterthe part still to search — the index advances
work happenson the way upon the way upon the way up
combines byprinting a digitadding a character, or notadding 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.

56. The pattern to carry away

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 stringfor an array
base case: length is 0base case: index >= length
current: charAt(0)current: nums[index]
rest: substring(1)rest: index + 1
empty answer: ""empty answer: 0

57. Check: countup

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);
    }
}
frameprintsorder
n = 0Blastoff!first
n = 11second
n = 22third

Check your understanding

What does countup(2) display?

  • A. Blastoff!, 1, 2 (correct)
  • B. 2, 1, Blastoff!
  • C. 1, 2, Blastoff!
  • D. Blastoff!, 2, 1

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.

Why B tempts people
That is countdown's output, where the println comes BEFORE the recursive call and so runs on the way down.
Why C tempts people
This omits Blastoff! from the front — the base case is reached first and prints before any number.
Why D tempts people
This has the numbers in the order the calls started rather than the order they return.

58. Check: binary conversion

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
remainderposition
0 (first computed)rightmost
1middle
1 (last computed)leftmost

Check your understanding

What is 6 in binary?

  • A. 110 (correct)
  • B. 011
  • C. 101
  • D. 10

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.

Why B tempts people
This reads the remainders in the order they were computed, which reverses the digits — 011 is 3, not 6.
Why C tempts people
This is 5 in binary (4 + 0 + 1), not 6.
Why D tempts people
This is 2 in binary; the conversion has three steps, so the answer has three digits.

59. Check: the recursive result

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;
}
indexnums[index]contributes
07+1
17+1
23+0

Check your understanding

For the array [7, 7, 3] starting at index 0, what is returned?

  • A. 2 (correct)
  • B. 1
  • C. 3
  • D. 0

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.

Why B tempts people
This would be the result if a call returned 1 instead of recurse + 1, discarding what the recursion found.
Why C tempts people
This counts all three elements rather than only those equal to 7.
Why D tempts people
The base case returns 0, but the frames above it add their contributions before the value reaches the caller.

60. The stack as a free reversal

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.

61. How sure are you?

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?

  • The digits would be printed in reverse order
  • Nothing would change
  • It would print a leading zero
  • It would overflow the stack

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.

62. Explain it to someone else

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.

63. Exit ticket

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?

  • Whether the work is written before or after the recursive call
  • Whether the method returns a value or is void
  • Whether the base case comes first or last
  • The depth of the recursion

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.

64. Draw the whole lesson

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.

65. Recap

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 thingit is this
about orderbefore the call is down, after the call is up
about sequencesbase case, current item, rest, combine
about arraysadvance an index rather than copying a shorter array

Sources

  1. 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
  2. Java SE 21 API — Integer.toBinaryString
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108