Skip to main content
Java · lesson 6 of 66
Open the Java page →
Java beginner Lesson 6 of 66

Control Flow in Java

Master Java's decision-making and looping constructs — if/else, switch expressions, for/while/do-while loops, break, and continue.

By SkillByExample · Updated

Control flow decides which statements run and how often. By default Java runs top to bottom; conditionals branch and loops repeat. Every example is a single file run with the JDK 21 source launcher (java File.java), so you can paste it and run it unchanged.

if / else if / else

Java tests each condition in order and runs the first branch that is true; the rest are skipped.

public class Grades {
    public static void main(String[] args) {
        int[] scores = {95, 82, 82, 70, 41};
        for (int score : scores) {
            String grade;
            if (score >= 90) {
                grade = "A";
            } else if (score >= 80) {
                grade = "B";
            } else if (score >= 70) {
                grade = "C";
            } else {
                grade = "F";
            }
            System.out.println(score + " -> " + grade);
        }
    }
}
$ java Grades.java
95 -> A
82 -> B
82 -> B
70 -> C
41 -> F

95 also satisfies score >= 80 and score >= 70, but only the first true branch runs, so the order of the tests matters: put the narrowest range first.

Missing braces

Braces are optional around a single statement, which makes this a classic bug:

public class Braces {
    public static void main(String[] args) {
        int balance = 50;
        if (balance < 0)
            System.out.println("Overdrawn!");
            System.out.println("Fee charged");
        System.out.println("Balance: " + balance);
    }
}
$ java Braces.java
Fee charged
Balance: 50

The indentation suggests both lines belong to the if, but only the first does. “Fee charged” prints even though the account is not overdrawn. Always use braces.

Classic switch and fall-through

The colon form of switch jumps to the matching case and keeps running until a break.

public class FallThrough {
    public static void main(String[] args) {
        int day = 3;
        switch (day) {
            case 1:
                System.out.println("Monday");
            case 3:
                System.out.println("Wednesday");
            case 4:
                System.out.println("Thursday");
                break;
            default:
                System.out.println("Other");
        }
    }
}
$ java FallThrough.java
Wednesday
Thursday

case 3 has no break, so execution falls into case 4 and prints “Thursday” too. Each case needs its own break — or use the arrow form below, which never falls through.

switch expressions (Java 14+)

The arrow form yields a value, groups labels with commas, and runs only the matching branch.

public class DayNames {
    public static void main(String[] args) {
        for (int day : new int[] {3, 6, 9}) {
            String name = switch (day) {
                case 1 -> "Monday";
                case 2 -> "Tuesday";
                case 3 -> "Wednesday";
                case 4 -> "Thursday";
                case 5 -> "Friday";
                case 6, 7 -> "Weekend";
                default -> "Invalid day " + day;
            };
            System.out.println(name);
        }
    }
}
$ java DayNames.java
Wednesday
Weekend
Invalid day 9

Because a switch expression must produce a value, the compiler rejects it if some input has no branch. For an int that means a default is required; for an enum listing every constant is enough.

yield in a block

When a branch needs several statements, use a block and yield the value.

public class Yield {
    public static void main(String[] args) {
        for (int score : new int[] {82, 64, 30}) {
            String result = switch (score / 10) {
                case 10, 9 -> "A";
                case 8 -> "B";
                case 7 -> "C";
                default -> {
                    System.out.println("  (below 70, checking pass mark for " + score + ")");
                    yield score >= 60 ? "D" : "F";
                }
            };
            System.out.println(score + " -> " + result);
        }
    }
}
$ java Yield.java
82 -> B
  (below 70, checking pass mark for 64)
64 -> D
  (below 70, checking pass mark for 30)
30 -> F

score / 10 is integer division, so 82 becomes 8 and matches case 8 directly. The default block only runs for 64 and 30. switch also works on String, char and enum values, and (since Java 21) on patterns.

for loops

Use a for loop when you know how many iterations you need: initialise, test, update.

public class ForLoops {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) System.out.print(i + " ");
        System.out.println();

        for (int i = 10; i > 0; i -= 3) System.out.print(i + " ");
        System.out.println();

        for (int i = 0, j = 4; i < j; i++, j--) System.out.println("i=" + i + " j=" + j);

        String[] fruits = {"apple", "banana", "cherry"};
        for (String fruit : fruits) System.out.print(fruit.toUpperCase() + " ");
        System.out.println();
    }
}
$ java ForLoops.java
0 1 2 3 4
10 7 4 1
i=0 j=4
i=1 j=3
APPLE BANANA CHERRY

The countdown stops at 1 because the next value, -2, fails i > 0. The two-variable loop stops when i and j meet at 2. The enhanced for (for-each) walks an array or collection without an index — use it unless you need the position.

while and do-while

while checks first, so its body may run zero times. do-while runs the body once before checking.

public class Whiles {
    public static void main(String[] args) {
        int n = 100;
        while (n < 10) {
            System.out.println("while body ran");
        }
        do {
            System.out.println("do-while body ran with n=" + n);
        } while (n < 10);

        int num = 1234, digitSum = 0;
        while (num > 0) {
            digitSum += num % 10;
            num /= 10;
        }
        System.out.println("digit sum: " + digitSum);
    }
}
$ java Whiles.java
do-while body ran with n=100
digit sum: 10

The while body never ran because 100 < 10 was false from the start; the do-while body ran once anyway. The digit-sum loop doesn’t know in advance how many digits there are, which is when while fits better than for.

Reading input until a sentinel

A while loop is the natural way to read until a stop value. Here the input is piped in.

import java.util.Scanner;

public class SumInput {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int sum = 0;
        int input = sc.nextInt();          // priming read
        while (input != 0) {
            sum += input;
            input = sc.nextInt();
        }
        System.out.println("Sum: " + sum);
    }
}
$ printf "10\n20\n5\n0\n" | java SumInput.java
Sum: 35

The read before the loop gives the condition something to test on the first pass; the read at the end of the body fetches the next value. The 0 ends the loop and is never added.

break, continue and labels

break leaves a loop, continue skips to the next iteration, and a labelled break leaves an outer loop.

public class BreakContinue {
    public static void main(String[] args) {
        int[] nums = {7, 3, 9, 4, 11, 2};
        for (int n : nums) {
            if (n % 2 == 0) {
                System.out.println("first even: " + n);
                break;
            }
        }

        for (int i = 1; i <= 10; i++) {
            if (i % 3 != 0) continue;
            System.out.print(i + " ");
        }
        System.out.println();

        outer:
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (i * j == 6) {
                    System.out.println("found i=" + i + " j=" + j);
                    break outer;
                }
            }
        }
    }
}
$ java BreakContinue.java
first even: 4
3 6 9
found i=2 j=3

break stopped at 4, so 2 was never examined. Without the outer label, break would leave only the inner loop and the search would continue, also finding i=3 j=2.

Project: number guessing game

A seeded Random makes the secret repeatable, and the guesses are piped in so the run is reproducible.

import java.util.Random;
import java.util.Scanner;

public class GuessingGame {
    public static void main(String[] args) {
        int secret = new Random(42).nextInt(100) + 1;
        Scanner sc = new Scanner(System.in);
        int attempts = 0, guess;
        do {
            guess = sc.nextInt();
            attempts++;
            if (guess < secret)      System.out.println(guess + " is too low");
            else if (guess > secret) System.out.println(guess + " is too high");
            else                     System.out.println(guess + " is correct in " + attempts + " attempts");
        } while (guess != secret);
    }
}
$ printf "50\n25\n37\n" | java GuessingGame.java
50 is too high
25 is too low
37 is too high
Exception in thread "main" java.util.NoSuchElementException
	at java.base/java.util.Scanner.throwFor(Scanner.java:945)
	at java.base/java.util.Scanner.next(Scanner.java:1602)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2267)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2221)
	at GuessingGame.main(GuessingGame.java:10)

The input ran out before the secret was found, and nextInt() throws NoSuchElementException at end of input. The secret is between 26 and 36, so halving the gap again finds it:

$ printf "50\n25\n37\n31\n" | java GuessingGame.java
50 is too high
25 is too low
37 is too high
31 is correct in 4 attempts

Halving the remaining range each time is binary search; it finds any number from 1 to 100 in at most 7 guesses. The do-while fits because a guess must be read before there is anything to compare.

Practice

  1. Print FizzBuzz for 1 to 15: “Fizz” for multiples of 3, “Buzz” for multiples of 5, “FizzBuzz” for both, the number otherwise.
Solution
public class FizzBuzz {
    public static void main(String[] args) {
        for (int i = 1; i <= 15; i++) {
            if (i % 15 == 0) System.out.println("FizzBuzz");
            else if (i % 3 == 0) System.out.println("Fizz");
            else if (i % 5 == 0) System.out.println("Buzz");
            else System.out.println(i);
        }
    }
}
$ java FizzBuzz.java
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

i % 15 must be tested first; otherwise 15 would match % 3 and print “Fizz”.

  1. Print a right triangle and a pyramid of * with 4 rows using nested loops.
Solution
public class Patterns {
    public static void main(String[] args) {
        int n = 4;
        for (int i = 1; i <= n; i++) {
            System.out.println("*".repeat(i));
        }
        for (int i = 1; i <= n; i++) {
            for (int s = i; s < n; s++) System.out.print(" ");
            for (int k = 1; k <= 2 * i - 1; k++) System.out.print("*");
            System.out.println();
        }
    }
}
$ java Patterns.java
*
**
***
****
   *
  ***
 *****
*******

Row i of the pyramid has n - i spaces and 2i - 1 stars, so each row grows by two stars and shrinks by one space.

  1. Change break outer; in BreakContinue to plain break; and print every match. How many lines appear?
Solution
public class InnerBreak {
    public static void main(String[] args) {
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                if (i * j == 6) {
                    System.out.println("found i=" + i + " j=" + j);
                    break;
                }
            }
        }
    }
}
$ java InnerBreak.java
found i=2 j=3
found i=3 j=2

The unlabelled break leaves only the j loop, so the i loop continues and finds the second match.

  1. Instead of halving, guess in steps of 10 and then step down: feed 10 20 30 40 35 32 31 to the guessing game. How many attempts does it take?
Solution
import java.util.Random;
import java.util.Scanner;

public class GuessingGame {
    public static void main(String[] args) {
        int secret = new Random(42).nextInt(100) + 1;
        Scanner sc = new Scanner(System.in);
        int attempts = 0, guess;
        do {
            guess = sc.nextInt();
            attempts++;
            if (guess < secret)      System.out.println(guess + " is too low");
            else if (guess > secret) System.out.println(guess + " is too high");
            else                     System.out.println(guess + " is correct in " + attempts + " attempts");
        } while (guess != secret);
    }
}
$ printf "10\n20\n30\n40\n35\n32\n31\n" | java GuessingGame.java
10 is too low
20 is too low
30 is too low
40 is too high
35 is too high
32 is too high
31 is correct in 7 attempts

Seven attempts against four for halving. The loop ends as soon as guess != secret is false, so it reads exactly as many numbers as it needs.

Frequently Asked Questions

When should I use switch instead of if-else?
Use switch when you're comparing one variable against multiple constant values. It's cleaner than a long if-else chain. Java 14+ switch expressions are especially concise, never fall through, and must produce a value for every possible input.
What is the difference between while and do-while?
A while loop checks the condition before running — it may never execute. A do-while loop runs the body first, then checks — it always executes at least once. Use do-while when you need to run the body at least one time (e.g., input validation).
What does break do in a nested loop?
break exits only the innermost loop it's inside. To break out of an outer loop, use a labelled break: label: for(...) { for(...) { break label; } }
What is switch fall-through in Java?
In a classic switch statement with case labels and colons, execution continues into the next case unless a break stops it. The arrow form (case X ->) introduced in Java 14 never falls through, which removes a whole class of bugs.