Skip to content

03 · Control Flow

if / else

int age = 20;

if (age < 13) {
    System.out.println("Child");
} else if (age < 20) {
    System.out.println("Teenager");
} else {
    System.out.println("Adult");
}

switch statement

Classic switch falls through unless you break:

int day = 3;
String name;

switch (day) {
    case 1:
        name = "Monday";
        break;
    case 2:
        name = "Tuesday";
        break;
    case 3:
        name = "Wednesday";
        break;
    default:
        name = "Unknown";
}

System.out.println(name);   // Wednesday

Modern switch expression

Java 14+ supports switch as an expression with arrow syntax — no fall-through, and it returns a value directly:

int day = 3;

String name = switch (day) {
    case 1 -> "Monday";
    case 2 -> "Tuesday";
    case 3 -> "Wednesday";
    case 4, 5 -> "Almost the weekend";
    default -> "Unknown";
};

System.out.println(name);   // Wednesday

while and do-while loops

int count = 0;
while (count < 3) {
    System.out.println("count = " + count);
    count++;
}
// count = 0, count = 1, count = 2

int n = 0;
do {
    System.out.println("runs at least once: " + n);
    n++;
} while (n < 0);   // condition is false immediately, but body already ran once

for loops

// Classic indexed for loop
for (int i = 0; i < 5; i++) {
    System.out.println("i = " + i);
}

// Enhanced for-each loop (arrays, collections)
int[] scores = {90, 85, 77};
for (int score : scores) {
    System.out.println("score: " + score);
}

break and continue

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break;       // exit the loop entirely
    }
    if (i % 2 == 0) {
        continue;    // skip to the next iteration
    }
    System.out.println(i);   // prints 1, 3
}
Construct Use when
if / else branching on a boolean condition
switch branching on one variable against many discrete values
while repeat while a condition holds, unknown iteration count
do-while same, but body always runs at least once
for repeat a known number of times, or with an index
for-each iterate every element of an array/collection

How It Actually Works

Every if/else, while, and for you write compiles down to conditional jump bytecodes operating on the operand stack — if_icmpge, ifeq, goto — there is no structured-control-flow concept inside the class file at all; it's all comparisons and jumps to byte offsets, exactly like assembly. The compiler's job is to turn nested braces into a flat, jump-target-labeled instruction stream.

The JIT compiler treats loop bodies specially: methods with hot loops can get on-stack replacement (OSR) — the JVM compiles just that loop to native code and transplants the interpreter's live locals into the compiled frame while the loop is still running, without waiting for the method to be called again. This is why a single long-running loop in main can still benefit from JIT optimization even though main itself is called only once.

switch on a String is not a special bytecode — the compiler desugars it into two nested switches: the first switches on String.hashCode() (a fast tableswitch/lookupswitch over ints), and inside each hash bucket a chain of .equals() calls disambiguates collisions before jumping to the matching case body. That's why hashCode() consistency matters even though you never call it directly in a switch statement.

🔀 See this in another language

Exercise

Write a program that loops from 1 to 100 and, for each number: prints "Fizz" if divisible by 3, "Buzz" if divisible by 5, "FizzBuzz" if divisible by both, and the number itself otherwise (classic FizzBuzz). Implement it once with a classic for loop and if/else, then rewrite the divisibility check using a switch expression.