Java flow control exercises — the entry grade, the library and a three-attempt ATM (and the 9.5 that came out as “Pass”)

A classmate once showed me his grade validator, very proud of it: “it works, I’ve tested it”. He had tested it with a 6. I asked him to enter a 9.5 and the program answered, without batting an eyelid: “Pass”. No error, no exception, no compiler warning. Each condition was correctly written on its own; what was wrong was the order: the first one was grade >= 5, and 9.5 is also greater than or equal to 5. In an else if chain the first true condition wins and the rest are not even looked at. You’ll see that bug with its real output in “What’s wrong here?”, and the first exercise forces you to order them properly.

Here are three Java flow control exercises, from easier to harder: the university entry grade (ranges with else if and validating before calculating), the faculty library loan (switch on a String, each case with its own braces) and an ATM with three PIN attempts without loops and one operation with switch. As in the flow control practice, there are no loops or arrays: each run does just one thing. At the end you have the exam-style quiz and the cheat sheet.

How to use this article

  1. Read the statement and the expected output of each exercise. The output is real: if your program prints the same with the same data, you’re on the right track.
  2. Try it without looking at anything for at least 15 minutes. If you get stuck, open the hint.
  3. Before the solutions, do “What’s wrong here?”: three fragments with one bug each. Predict what they print before reading the explanation.
  4. Compare your solution with the commented one. It doesn’t have to be identical: it has to give the same output with the same data and for the same reasons.

The outputs come from a single run with Java 21 on a computer set to Spanish, so decimals are typed and printed with a comma (11,8 instead of 11.8): Scanner and printf follow the computer’s language. On a computer set to English you would type 11.8 and see a point. What appears after each question is what was typed. In the site’s Java playground the typed data goes in the Input tab, one line per value.

Basic level

Exercise 1 — University entry grade validator

Write GradeValidator: it asks for a university entry grade (from 0 to 14, as in Spain’s university entrance exam with the optional subjects) and rates it like this:

GradeRating
below 5Fail
from 5 to below 7Pass
from 7 to below 9Good
from 9 to below 11Very good
11 or moreExcellent

Then it says how far above or below the GCID cut-off grade it is, which we’ll take as 10.5 (made up), with two decimals. If the grade is outside 0-14, it warns and calculates nothing else. Finally it prints the unrounded margin with println, to see what the double really stores.

Expected output with 11,8:

=== ENTRY GRADE VALIDATOR ===
Entry grade (0-14): 11,8

--- Result ---
Rating: Excellent
You get into GCID: 1,30 above the cut-off (10,5).
Unrounded margin: 1.3000000000000007

With 15:

=== ENTRY GRADE VALIDATOR ===
Entry grade (0-14): 15
Invalid data: the entry grade goes from 0 to 14.

With 4,99:

=== ENTRY GRADE VALIDATOR ===
Entry grade (0-14): 4,99

--- Result ---
Rating: Fail
You are 5,51 short of the cut-off (10,5).
Unrounded margin: -5.51

Validate the range with an if at the start and stop with return; if it fails. For the rating, an else if chain that starts with the highest range (grade >= 11): that way each condition only needs the lower limit. The margin is grade - CUTOFF: if it’s negative, print -margin in the shortfall message. Why 1,30 with printf and something else with println? You’ll see in the solution.

Intermediate level

Exercise 2 — Library menu

Write Library: the faculty library handles one operation per run, chosen from a menu.

  • 1. Borrow a book: asks for the member type, how many books they already have and the title. By type: student 15 days and 3 books at most; PDI (teaching and research staff) 30 days and 6 books; visitor 7 days and 1 book. If the type doesn’t exist, the title is missing or they already have the maximum, it says so; otherwise it confirms the loan.
  • 2. Return a book: asks for the member type and the days late. On time, it thanks them; PDI staff pay no fine; everyone else pays 0.20 € per day up to 5 €.
  • 3. See the rules.
  • Any other number: “Invalid option”.

The member type is accepted in any mix of upper and lower case (PDI, pdi). The rules are made up.

Expected output for a loan and for a loan that doesn’t fit:

=== FACULTY LIBRARY ===
1. Borrow a book
2. Return a book
3. See the rules
Option: 1
Member type (student/pdi/visitor): student
Books you already have: 1
Title: Clean Code
Loan done: 'Clean Code' for 15 days (2 of 3 books).
=== FACULTY LIBRARY ===
1. Borrow a book
2. Return a book
3. See the rules
Option: 1
Member type (student/pdi/visitor): visitor
Books you already have: 1
Title: Java in Depth
You can't take any more: you already have 1 of 1.

Returns 9 days late, 40 days late (it hits the maximum) and from a PDI member typed in capitals:

=== FACULTY LIBRARY ===
1. Borrow a book
2. Return a book
3. See the rules
Option: 2
Member type (student/pdi/visitor): student
Days late: 9
Fine: 1,80 € (0.20 € per day, 5 € at most).
=== FACULTY LIBRARY ===
1. Borrow a book
2. Return a book
3. See the rules
Option: 2
Member type (student/pdi/visitor): student
Days late: 40
Fine: 5,00 € (0.20 € per day, 5 € at most).
=== FACULTY LIBRARY ===
1. Borrow a book
2. Return a book
3. See the rules
Option: 2
Member type (student/pdi/visitor): PDI
Days late: 12
Returned 12 days late: PDI staff pay no fine.

And an option that doesn’t exist:

=== FACULTY LIBRARY ===
1. Borrow a book
2. Return a book
3. See the rules
Option: 5
Invalid option: 5

The menu is a switch (option). Inside case 1, another switch (kind) on a String that gives a value to days and maxBooks (with its break in each case, or with arrows). If you declare a variable with the same name (kind) in cases 1 and 2, put each case inside its own braces: case 1: { … break; }. Read the type with trim().toLowerCase() so you don’t depend on capitals. The fine: delay * 0.20 and, if it goes over 5, it stays at 5.

Final challenge

Exercise 3 — ATM with PIN

Write AtmPin: the right PIN is 4321 and there are three attempts without using loops. After each failure it says how many attempts are left; on the third failure, “Card blocked. Call your bank.” and the program ends. With the right PIN, a menu with one operation:

  • 1. Check balance (it starts at 500.00 €).
  • 2. Withdraw cash: the amount must be positive and a multiple of 10, at most 600 € per operation, and no more than the balance. Check in that order.
  • 3. Deposit cash: from 1 to 1000 € per deposit.

Always read lines (nextLine() + Integer.parseInt), and store the PIN as text. Expected output getting it right on the second try and withdrawing 120 €:

=== ATM ===
PIN: 1111
Wrong PIN (2 attempts left)
PIN: 4321
Correct PIN.

1. Check balance
2. Withdraw cash
3. Deposit cash
Operation: 2
Amount (multiple of 10): 120
Take 120 €. Balance: 380,00 €

Three failures:

=== ATM ===
PIN: 1111
Wrong PIN (2 attempts left)
PIN: 2222
Wrong PIN (1 attempt left)
PIN: 0000
Card blocked. Call your bank.

Without enough balance, with an amount that can’t be given in notes and with a deposit:

=== ATM ===
PIN: 4321
Correct PIN.

1. Check balance
2. Withdraw cash
3. Deposit cash
Operation: 2
Amount (multiple of 10): 550
Insufficient balance: you have 500,00 €.
=== ATM ===
PIN: 4321
Correct PIN.

1. Check balance
2. Withdraw cash
3. Deposit cash
Operation: 2
Amount (multiple of 10): 125
Invalid amount: the machine only gives 10, 20 and 50 notes.
=== ATM ===
PIN: 4321
Correct PIN.

1. Check balance
2. Withdraw cash
3. Deposit cash
Operation: 3
Amount to deposit: 200
Deposit done. Balance: 700,00 €

Without loops, the three attempts are three if (!pin.equals(PIN)), each one inside the previous one: the second PIN is only asked for if the first one failed. In the deepest one, block and return;. If you reach the line after the first if, some attempt was right. For the plural, attempts is 2 after the first failure and 1 after the second. Why "4321" and not 4321? Think of a PIN like 0420.

What’s wrong here?

Before the solutions, three fragments with one bug each: all three compile without warnings. Read each one and predict what it prints before looking at the output.

Mistake 1

public class ElseIfOrder {
    public static void main(String[] args) {
        double grade = 9.5;
        String rating;
        if (grade >= 5) {
            rating = "Pass";
        } else if (grade >= 7) {
            rating = "Good";
        } else if (grade >= 9) {
            rating = "Very good";
        } else {
            rating = "Fail";
        }
        System.out.println(grade + " → " + rating);
    }
}

What does it print with grade = 9.5? Real output:

9.5 → Pass

9.5 >= 5 is true, so it goes into the first branch and the chain ends there: the following else if are not evaluated. The diagram shows it on the grade line:

The first true condition winsGrade line from 0 to 14. If the first condition is grade >= 5, everything from 5 to 14 stays as Pass and 9.5 never reaches Very good. From highest to lowest, each range has its own name and 9.5 lands in Very good.Wrong order: if (grade >= 5) firstFailPass: swallows everythingRight order: from highest to lowestFailPassGoodVery goodExcellent05791114grade = 9.5
In the reverse order, the first if catches everything above 5 and the others never get looked at.

With grade >= 5 at the top, everything from 5 to 14 stays as “Pass”: the “Good” and “Very good” branches are dead code.

Ordering the ranges from lowest to highest using only the lower limit. Each condition is correct on its own, but the first one swallows everything. Notice the 9.5 in the output: it’s printed by + with a double, which always uses a point.

Fix: from highest to lowest.

public class ElseIfOrder {
    public static void main(String[] args) {
        double grade = 9.5;
        String rating;
        if (grade >= 9) {                   // from highest to lowest
            rating = "Very good";
        } else if (grade >= 7) {
            rating = "Good";
        } else if (grade >= 5) {
            rating = "Pass";
        } else {
            rating = "Fail";
        }
        System.out.println(grade + " → " + rating);
    }
}
9.5 → Very good

If you prefer going from lowest to highest, each condition needs both limits: grade >= 5 && grade < 7. It works, but it’s longer, and if you get a limit wrong, gaps appear (what happens to a 6.99 if you write grade <= 6.9?). From highest to lowest, no gap is possible.

Mistake 2

public class LoanDays {
    public static void main(String[] args) {
        String kind = "student";
        int days = 0;
        switch (kind) {
            case "student":
                days = 15;
            case "pdi":
                days = 30;
            case "visitor":
                days = 7;
        }
        System.out.println(kind + ": " + days + " days");
    }
}

How many days does a student get? Real output:

student: 7 days

The break is missing in each case. Java enters case "student", sets days = 15, falls into case "pdi" and changes it to 30, falls into case "visitor" and leaves it at 7. The last value is the one that stays. It’s the same fall-through that added up instalments on purpose in the practice; here it wrecks the result.

Thinking the switch leaves on its own when a case ends. In the classic switch it only leaves with break (or by reaching the end). Without it, every case below runs.

Fix: the arrow switch, which also returns the value.

public class LoanDays {
    public static void main(String[] args) {
        String kind = "student";
        int days = switch (kind) {         // with arrows there is no fall-through
            case "student" -> 15;
            case "pdi" -> 30;
            case "visitor" -> 7;
            default -> 0;                  // in a switch that returns a value, default is mandatory
        };
        System.out.println(kind + ": " + days + " days");
    }
}
student: 15 days

Mistake 3

public class ValidAge {
    public static void main(String[] args) {
        int age = 150;
        if (age < 16 && age > 99) {
            System.out.println("Age out of range");
        } else {
            System.out.println("Age correct: " + age);
        }
    }
}

Does it reject 150 years? Real output:

Age correct: 150

age < 16 && age > 99 asks for the age to be below 16 and at the same time above 99: no number meets both, so the condition is always false and everything falls into the else. With 150: 150 < 16 is false, and with && Java doesn’t even look at the second part (short circuit).

Translating “out of range” with &&. “Out” means “below or above”: ||. If you think of it in positive form (“inside” = age >= 16 && age <= 99), outside is its negation: !(age >= 16 && age <= 99), which by De Morgan’s laws is age < 16 || age > 99.

Fix:

public class ValidAge {
    public static void main(String[] args) {
        int age = 150;
        if (age < 16 || age > 99) {      // it is enough for ONE of the two to fail
            System.out.println("Age out of range");
        } else {
            System.out.println("Age correct: " + age);
        }
    }
}
Age out of range

Commented solutions

Solution Exercise 1

import java.util.Scanner;

/**
 * University entry grade (0-14): check the range BEFORE calculating, and rate it
 * with else if from highest to lowest. Cut-off grade made up.
 */
public class GradeValidator {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final double CUTOFF = 10.5;                 // GCID cut-off grade (made up)

        System.out.println("=== ENTRY GRADE VALIDATOR ===");
        System.out.print("Entry grade (0-14): ");
        double grade = sc.nextDouble();             // computer set to Spanish: typed with a comma (11,8)

        // 1. Validate first: if the data makes no sense, stop here and calculate nothing else
        if (grade < 0 || grade > 14) {
            System.out.println("Invalid data: the entry grade goes from 0 to 14.");
            return;
        }

        // 2. Rate from HIGHEST to lowest: the first true condition wins and the rest are not even looked at.
        //    That is why "grade >= 9" is enough and "grade >= 9 && grade < 11" is not needed.
        String rating;
        if (grade >= 11) {
            rating = "Excellent";
        } else if (grade >= 9) {
            rating = "Very good";
        } else if (grade >= 7) {
            rating = "Good";
        } else if (grade >= 5) {
            rating = "Pass";
        } else {
            rating = "Fail";
        }
        System.out.println();
        System.out.println("--- Result ---");
        System.out.println("Rating: " + rating);

        // 3. Margin over the cut-off: positive if you get in, negative if you fall short
        double margin = grade - CUTOFF;
        if (margin >= 0) {
            System.out.printf("You get into GCID: %.2f above the cut-off (%.1f).%n", margin, CUTOFF);
        } else {
            System.out.printf("You are %.2f short of the cut-off (%.1f).%n", -margin, CUTOFF);
        }
        // println ignores the computer's language: decimal point and every decimal the double has
        System.out.println("Unrounded margin: " + margin);
        sc.close();
    }
}

Why it’s done this way:

  • Validate first and stop with return. If 15 carried on, it would come out as “Excellent” with a margin of 4.50: a result that looks right from impossible data. With the return at the start, everything after it can take for granted that the grade is between 0 and 14.
  • From highest to lowest. With 11,8 the first condition (11.8 >= 11) is already true: “Excellent”. With 4,99 all four fail and it reaches the else: “Fail”. No value between 0 and 14 is left without a rating.
  • String rating; with no initial value. Java checks that every branch gives it a value before it’s used. If you remove the final else, javac complains: variable rating might not have been initialized. It’s a help: it warns you there are unrated grades.
  • The margin with printf and with println. 11.8 − 10.5 should be 1.3, but in binary 11.8 isn’t exact and the double stores 1.3000000000000007. printf("%.2f") rounds to two decimals and uses the computer’s comma (1,30); println shows the number as it is, with a point, because it ignores the language. With 4,99 the result is −5.51 and here it does come out clean. That’s why printf is the way to show results.
  • -margin in the shortfall message. “You are -5,51 short” reads oddly; with the sign flipped, “You are 5,51 short”.
Typed gradeIn range?First true conditionRatingMargin
11,8Yesgrade >= 11Excellent11.8 − 10.5 = 1.30
15No: 15 > 14(not reached)(not reached)(not reached)
4,99Yesnone: elseFail4.99 − 10.5 = −5.51

Solution Exercise 2

import java.util.Scanner;

/**
 * Faculty library: one operation per run (loan, return or rules), no arrays.
 * Loan periods, limits and fines made up.
 */
public class Library {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("=== FACULTY LIBRARY ===");
        System.out.println("1. Borrow a book");
        System.out.println("2. Return a book");
        System.out.println("3. See the rules");
        System.out.print("Option: ");
        int option = sc.nextInt();
        sc.nextLine();                             // Enter out before reading texts

        switch (option) {
            case 1: {                              // own braces: "kind" is also declared in case 2
                System.out.print("Member type (student/pdi/visitor): ");
                String kind = sc.nextLine().trim().toLowerCase();
                System.out.print("Books you already have: ");
                int has = sc.nextInt();
                sc.nextLine();
                System.out.print("Title: ");
                String title = sc.nextLine().trim();

                int days;                          // loan days
                int maxBooks;                      // books at the same time
                switch (kind) {                    // switch with String: compares with equals(), not with ==
                    case "student":
                        days = 15;
                        maxBooks = 3;
                        break;
                    case "pdi":
                        days = 30;
                        maxBooks = 6;
                        break;
                    case "visitor":
                        days = 7;
                        maxBooks = 1;
                        break;
                    default:
                        days = 0;                  // marker for "unknown kind"
                        maxBooks = 0;
                }

                if (days == 0) {
                    System.out.println("Unknown member type: " + kind);
                } else if (title.isEmpty()) {
                    System.out.println("The title is missing.");
                } else if (has >= maxBooks) {
                    System.out.println("You can't take any more: you already have " + has + " of " + maxBooks + ".");
                } else {
                    System.out.printf("Loan done: '%s' for %d days (%d of %d books).%n",
                            title, days, has + 1, maxBooks);
                }
                break;
            }
            case 2: {
                System.out.print("Member type (student/pdi/visitor): ");
                String kind = sc.nextLine().trim().toLowerCase();
                System.out.print("Days late: ");
                int delay = sc.nextInt();

                if (delay < 0) {
                    System.out.println("Invalid data.");
                } else if (delay == 0) {
                    System.out.println("Returned on time. Thank you.");
                } else if (kind.equals("pdi")) {
                    System.out.println("Returned " + delay + " days late: PDI staff pay no fine.");
                } else {
                    double fine = delay * 0.20;     // 20 cents per day...
                    if (fine > 5) {
                        fine = 5;                   // ...up to a maximum of 5 €
                    }
                    System.out.printf("Fine: %.2f € (0.20 € per day, 5 € at most).%n", fine);
                }
                break;
            }
            case 3:
                System.out.println("Students: 3 books, 15 days.");
                System.out.println("PDI: 6 books, 30 days.");
                System.out.println("Visitors: 1 book, 7 days.");
                System.out.println("Late: 0.20 € per day, 5 € at most (PDI staff don't pay).");
                break;
            default:
                System.out.println("Invalid option: " + option);
        }
        sc.close();
    }
}

Why it’s done this way:

  • Own braces in case 1: { … } and case 2: { … }. The whole switch is a single block: if both cases declared String kind without braces, javac would say variable kind is already defined. With braces, each kind only lives inside its own case. Notice the break goes inside the braces, at the end.
  • The inner switch gives a value to two variables. That’s why it’s classic (with break) and not an expression: a switch expression returns one value. The default sets days = 0 as a marker for “unknown type”, just like the certificate’s -1 in the practice.
  • The order of the loan chain. First the type (if it doesn’t exist, maxBooks is 0 and “you already have 1 of 0” would make no sense), then the title, then the limit. With the visitor: 1 >= 1 is true, so they can’t take another one.
  • has + 1 in the message. They had 1 and take another: 2 of 3.
  • The fine with a cap. 9 × 0.20 = 1.80 €; 40 × 0.20 = 8.00 €, which is over 5 and stays at 5.00 €. A one-line alternative is Math.min(delay * 0.20, 5).
  • PDI in capitals works because it’s lowercased when read; without toLowerCase(), "PDI".equals("pdi") would be false and the lecturer would pay a fine.

In case 2, sc.nextInt() is the last read, so the clean-up nextLine() isn’t needed: there’s nothing afterwards that could swallow the Enter. In case 1 it is, because the title comes next.

Solution Exercise 3

import java.util.Scanner;

/**
 * ATM: PIN with 3 attempts WITHOUT a loop (one if inside another) and one operation with switch.
 * Always read whole lines (nextLine + parseInt): that way there is no buffer trap.
 */
public class AtmPin {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        final String PIN = "4321";                 // String, not int: a PIN like 0420 would lose its zero
        final int LIMIT = 600;                     // euros per operation
        double balance = 500.0;
        int attempts = 3;

        System.out.println("=== ATM ===");
        System.out.print("PIN: ");
        String pin = sc.nextLine().trim();
        if (!pin.equals(PIN)) {                    // failure 1
            attempts--;
            System.out.println("Wrong PIN (" + attempts + " attempts left)");
            System.out.print("PIN: ");
            pin = sc.nextLine().trim();
            if (!pin.equals(PIN)) {                // failure 2
                attempts--;
                System.out.println("Wrong PIN (" + attempts + " attempt left)");
                System.out.print("PIN: ");
                pin = sc.nextLine().trim();
                if (!pin.equals(PIN)) {            // failure 3: that's it
                    System.out.println("Card blocked. Call your bank.");
                    return;                        // without this, the program would carry on to the menu
                }
            }
        }
        // If we get here, some attempt was right
        System.out.println("Correct PIN.");
        System.out.println();
        System.out.println("1. Check balance");
        System.out.println("2. Withdraw cash");
        System.out.println("3. Deposit cash");
        System.out.print("Operation: ");
        int op = Integer.parseInt(sc.nextLine().trim());

        switch (op) {
            case 1 -> System.out.printf("Balance: %.2f €%n", balance);
            case 2 -> {
                System.out.print("Amount (multiple of 10): ");
                int amount = Integer.parseInt(sc.nextLine().trim());
                // From the most general to the most specific: format, machine limit and, last, the balance
                if (amount <= 0 || amount % 10 != 0) {
                    System.out.println("Invalid amount: the machine only gives 10, 20 and 50 notes.");
                } else if (amount > LIMIT) {
                    System.out.println("Over the limit per operation (" + LIMIT + " €).");
                } else if (amount > balance) {
                    System.out.printf("Insufficient balance: you have %.2f €.%n", balance);
                } else {
                    balance -= amount;
                    System.out.printf("Take %d €. Balance: %.2f €%n", amount, balance);
                }
            }
            case 3 -> {
                System.out.print("Amount to deposit: ");
                int amount = Integer.parseInt(sc.nextLine().trim());
                if (amount > 0 && amount <= 1000) {
                    balance += amount;
                    System.out.printf("Deposit done. Balance: %.2f €%n", balance);
                } else {
                    System.out.println("Invalid amount (1 to 1000 € per deposit).");
                }
            }
            default -> System.out.println("Invalid operation.");
        }
        sc.close();
    }
}

Why it’s done this way:

  • Three nested if statements. The second PIN is only asked for if the first one failed, and the third if both failed: each attempt goes inside the previous one. The diagram shows the paths:
The PIN with three nested ifsThree diamonds in a row: each No moves to the next attempt taking one off attempts; each Yes goes down to the menu. The third No goes to Card blocked and return. In the real run 1111 (No) and 4321 (Yes) are typed.PIN 1 right?types 1111Noattempts–YesPIN 2 right?types 4321Noattempts–YesPIN 3 right?YesNoCorrect PIN → operations menu (switch)blockedreturn;Real run: 1111 (No) → 4321 (Yes). Without the return, the third «No» would fall into the menu.
Every Yes path ends at the same menu; the only one that does not, ends with return.
  • The return on blocking is essential. Without it, after the third failure the program would leave the three if statements and reach “Correct PIN.”. In the practice, the guessing game used the other style (one block after another and return on a hit); both are valid, and both are crying out for a loop.
  • The PIN as a String. With int, 0420 would be read as 420 and a three-digit PIN would pass as valid. Also, comparing with equals avoids a NumberFormatException if someone types letters.
  • Always reading lines. Integer.parseInt(sc.nextLine().trim()) leaves no Enter in the buffer, so the clean-up nextLine() isn’t needed anywhere.
  • The order of the withdrawal checks. First the format (positive and a multiple of 10), then the machine’s limit and finally the balance. With 550: it’s a multiple of 10 and not over 600, so it reaches the third one: 550 > 500, insufficient balance. With 125: 125 % 10 is 5, not 0, and it stops at the first one.
  • The sums. 500 − 120 = 380.00 €; 500 + 200 = 700.00 €.
TypedConditionResult
1111!"1111".equals("4321") → trueattempts 3 → 2, another one is asked
4321!"4321".equals("4321") → falseleaves the if statements: correct PIN
2, 120120 % 10 == 0, 120 <= 600, 120 <= 500balance 380.00 €

Exam-style quiz

Now test yourself as in the exam. The FP2 Java quiz works like this: 15 questions in 15 minutes covering the whole Java syllabus, with a few from each topic. Here are the flow control ones: answer without running anything, check and read the explanation of the ones you miss. These questions also make up the bank of the FP2 theory mock exam.

Question 1Single choice

What does this fragment print?

int mark = 7;
if (mark >= 5) System.out.print("A");
else if (mark >= 7) System.out.print("B");
else System.out.print("C");
System.out.println();
  1. A
  2. B
  3. AB
  4. C
Solution

Answer: A

The conditions are tested from top to bottom and the first one that holds runs. Since 7 >= 5, A is printed and else if (mark >= 7) is never evaluated. That’s why a chain of ranges starts with the most restrictive one.

Question 2Short answer

What does this fragment print?

int day = 2;
switch (day) {
    case 1: System.out.print("L");
    case 2: System.out.print("M");
    case 3: System.out.print("X"); break;
    default: System.out.print("?");
}
System.out.println();
Solution

Answer: MX

The classic switch jumps to case 2 and prints M, but as there’s no break it keeps falling into case 3 (prints X) until it finds the break. This is the famous fall-through: forgetting a break is one of the most common mistakes.

Question 3True or false

True or false?

  1. Java has no elif: you write else if.
  2. With int x, the condition if (x = 5) doesn’t compile.
  3. Since Java 7 you can switch on a String.
  4. a && b always evaluates both sides.
  5. In a switch with arrows (case 1 -> …) there’s no fall-through.
Solution
  • T — Java has no elif: you write else if.
  • T — With int x, the condition if (x = 5) doesn’t compile.
  • T — Since Java 7 you can switch on a String.
  • F — a && b always evaluates both sides.
  • T — In a switch with arrows (case 1 -> …) there’s no fall-through.

if (x = 5) assigns instead of comparing and the result is an int, not a boolean: Java won’t accept it (in C it would compile). && short-circuits: if the first part is false, the second isn’t evaluated. And the switch with -> only runs its own branch, with no need for break.

Question 4Short answer

What does this fragment print?

int a = 4, b = 9;
String r = (a > b) ? "a" : (b % 2 == 0 ? "b even" : "b odd");
System.out.println(r);
Solution

Answer: b odd

The ternary operator is condition ? ifTrue : ifFalse. 4 > 9 is false, so the second ternary is evaluated: 9 % 2 is 1, not 0, so the result is "b odd". It’s the equivalent of Python’s x if c else y.

Question 5Single choice

What does this fragment print?

String s = new String("hello");
if (s == "hello") System.out.println("equal");
else System.out.println("different");
  1. different
  2. equal
  3. It doesn’t compile
  4. It throws an exception
Solution

Answer: different

With objects, == checks whether both variables point to the same place in memory. To compare the text you use equals (or equalsIgnoreCase). With String the bug is sneaky because == sometimes gets it right by chance (equal literals share an object).

Question 6Short answer

What does this fragment print?

int x = 0;
if (x != 0 && 10 / x > 1) System.out.println("big");
else System.out.println("safe");
Solution

Answer: safe

x != 0 is false, so && already knows the whole thing is false and doesn’t evaluate 10 / x. Thanks to that there’s no ArithmeticException from dividing by zero. With & (no short-circuit) it would be evaluated and the program would crash.

Question 7Fill in the gaps

Fill in the rules of the Java if:

The condition always goes in ____ and must be of type ____. The block goes in ____; if you leave them out, only the ____ statement depends on the if.

Words:

  • brackets
  • boolean
  • first
  • square brackets
  • indentation
  • last
  • braces
  • int
Solution

The condition always goes in brackets and must be of type boolean. The block goes in braces; if you leave them out, only the first statement depends on the if.

Without braces, indentation means nothing to Java: only the first statement is inside the if and the following ones always run. That’s why braces are recommended even for a single line.

Question 8Short answer

What does this fragment print (Java 14 or later)?

String day = "sat";
String kind = switch (day) {
    case "sat", "sun" -> "weekend";
    default -> "weekday";
};
System.out.println(kind);
Solution

Answer: weekend

The modern switch can return a value and group several cases with commas. "sat" matches the first case, so kind is "weekend". Notice the final ;: the whole switch is an expression inside an assignment.

Question 9Match

Match each operator with what it does:

&&
||
!
==
%

Definitions:

  • Remainder of integer division
  • Logical OR with short-circuit
  • Power
  • Concatenation of conditions
  • Logical AND with short-circuit
  • Equality (on objects it compares references)
  • Negation
Solution
  • && → Logical AND with short-circuit
  • || → Logical OR with short-circuit
  • ! → Negation
  • == → Equality (on objects it compares references)
  • % → Remainder of integer division

In Java you don’t write and, or or not as in Python: they’re &&, || and !. Power isn’t an operator (Math.pow(a, b)) and % gives the remainder (9 % 2 is 1).

Question 10Single choice

What does this fragment print?

int t = 20;
if (t > 25)
    System.out.print("hot ");
    System.out.print("beach");
System.out.println();
  1. beach
  2. An empty line
  3. hot beach
  4. It doesn’t compile
Solution

Answer: beach

The indentation makes you think both lines are inside the if, but Java only looks at braces. As there are none, only print("hot ") depends on the if. It’s the bug of an if without braces that later gets an extra line.

Question 11Multiple · wrong ticks subtract

With int a = 5, b = 0;, which conditions are true?

  1. a > 3 && b == 0
  2. a < 3 || b == 0
  3. a % 2 == 1
  4. !(a == 5)
  5. b != 0 && a / b > 1
Solution

Answer: a > 3 && b == 0 · a < 3 || b == 0 · a % 2 == 1

&& needs both parts true; ||, at least one. 5 % 2 is 1, so the third is true. The last one is false without throwing any exception thanks to the short-circuit.

Watch it step by step in Python Tutor

Python Tutor (pythontutor.com) runs code line by line and marks which line runs at each step. Choose Java in the drop-down and paste this program, which rates the same 9.5 with the wrong order and with the right one:

public class TutorGrade {
    public static void main(String[] args) {
        double grade = 9.5;
        String wrong;
        if (grade >= 5) {
            wrong = "Pass";
        } else if (grade >= 9) {
            wrong = "Very good";
        } else {
            wrong = "Fail";
        }
        String right;
        if (grade >= 9) {
            right = "Very good";
        } else if (grade >= 5) {
            right = "Pass";
        } else {
            right = "Fail";
        }
        System.out.println(wrong + " / " + right);
    }
}

Output:

Pass / Very good

What to watch step by step:

  1. In the first chain, the arrow goes through if (grade >= 5), goes in and jumps straight to the String right; line: the else if lines are never visited.
  2. In the variables panel, wrong = "Pass" appears while grade is still 9.5.
  3. In the second chain, the first condition (grade >= 9) is already true: it goes in at the first one.
  4. Count the steps of each chain: both stop as soon as they find a true condition. What changes is which one they find first.

Cheat sheet: Java flow control

The essentials in 30 seconds: validate the data and stop with return before calculating; in an else if chain the first true condition wins, so the most restrictive goes first; texts are compared with equals(); “out of range” is || and “in range” is &&; in a classic switch each case needs its break (or use arrows); reading lines with nextLine() + parseInt avoids the buffer trap.

I want to…CodeResult
Check that it’s within a rangeif (x >= 0 && x <= 14)both at once
Detect that it’s outsideif (x < 0 || x > 14)one is enough
Rate by rangesif (n >= 11) … else if (n >= 9) … else …from highest to lowest
Compare textsa.equals(b) / a.equalsIgnoreCase(b)true if they say the same
Compare with a fixed text with no null risk"y".equalsIgnoreCase(r)never throws
End the program earlyreturn; inside mainit ends there
Choose by a valueswitch (op) { case 1 -> …; default -> …; }no fall-through
Calculate by a valueint d = switch (k) { case "a" -> 15; default -> 0; };default mandatory
Pile up on purposecase 0: x += p; case 1: x += p; break;falls from one case to the next
A variable of its own in a casecase 1: { String k = …; break; }own braces
Read a number without the trapInteger.parseInt(sc.nextLine().trim())leaves no Enter
A repeatable random numbernew Random(42).nextInt(100) + 11 to 100, always the same

Templates from the exercises:

ExercisePatternWith this dataGives
Entry gradevalidate + return, else if from highest to lowest, printf("%.2f")11,8Excellent, 1,30 above
Librarymenu switch with braces per case + switch on a Stringstudent, 1 book15 days (2 of 3 books)
Library (fine)if with a cap40 days late5,00 €
ATMthree nested if (!pin.equals(PIN)) + return1111, 4321, withdraw 120balance 380,00 €
SymptomLikely cause
High grades rated as lowelse if ordered from lowest to highest with only the lower limit
Two equal texts “don’t match”compared with == instead of equals()
A switch gives the value of the last casemissing break (fall-through)
Impossible data is accepted&& where `` should be (the condition is never true)
variable … is already defined in a switchtwo case labels declare the same variable without their own braces
variable … might not have been initializedsome branch of the if doesn’t give the variable a value
The program carries on after blockingmissing return;
InputMismatchException with a decimala point was typed on a computer set to Spanish (or the other way round)

Before handing it in, ask yourself:

  1. Have I tried a value from each branch, including the limits (5, 7, 9, 11) and one out of range?
  2. Are all texts compared with equals()?
  3. Does each case of a classic switch end in break, except those that fall through on purpose?
  4. Do I validate the data before using it (before substring, dividing or rating)?

That wraps up flow control. In the next topic, Java loops, the guessing game and the ATM stop copying their block three times: a while repeats whatever is needed.

Keep goingAll Java articles

Did this article help you? Here is how you can support SergioLearns →