BeginnerReading time: ~18 min

Control Structures

If-else, switch expressions, for, while, do-while loops, break, continue and labeled break

Control structures govern the execution flow of a program. Without them, every program would be a single sequential list of instructions.


1. Definition

What? Control structures are language constructs that determine the order in which statements are executed — conditionally, repeatedly, or by skipping certain blocks entirely.

Why? Every real-world program requires decisions and repetition. if, switch, for, while, and their companions are the building blocks of business logic, validation, data processing, and dynamic behavior.

Where?

  • Business rule evaluation (if/else, switch)
  • Collection and array traversal (for, for-each, while)
  • Input validation (do-while)
  • State machines and routing (switch expression)
  • Complex iteration control (break, continue, labeled break)

2. Core Concepts

2.1 Conditional Branching

Structure Description When to Use
if / else Basic conditional branch 1–3 branches, boolean condition
if / else if / else Multi-way conditional Multiple mutually exclusive conditions
switch (classic) Value-based branching One variable, several concrete values
switch expression (Java 14+) Value-returning switch Assigning to a variable, cleaner syntax
Ternary ? : Inline conditional Simple value assignment

2.2 Loops

Structure Description Typical Use
for Counter-based loop Known iteration count, index needed
for-each (for(T x : coll)) Iterator-based loop Full traversal of collection/array
while Pre-test loop Condition-dependent repetition
do-while Post-test loop At least one execution required

2.3 Control Transfer Statements

Keyword Effect
break Exits the current loop or switch
continue Skips the remainder of the current iteration
return Exits the method (optionally returning a value)
yield (Java 14+) Returns a value from a switch expression block
labeled break Exits a labeled outer loop
labeled continue Jumps to the next iteration of a labeled outer loop

2.4 Evolution of `switch`

Java 1.0classic switch (int, char)
Java 5    → enum support
Java 7    → String support
Java 14   → switch expression (preview → standard)
Java 17   → pattern matching in switch (preview)
Java 21   → pattern matching in switch (finalized)

2.5 Loop Invariants, Termination, and Intent

Good loop design is not just about syntax.

  • A loop invariant is something that remains true before and after each iteration
  • A termination condition explains why the loop must eventually stop
  • The intent of the loop should be obvious: traverse, search, retry, aggregate, or wait

These ideas matter because many real bugs come from loops that are technically valid, but semantically unclear.

For example, a while(true) loop is not automatically bad. It becomes bad when the exit path is hidden, the state changes are unclear, or interruption is ignored.


3. Practical Usage

Which Loop Should You Choose?

Situation Recommended Structure
Full array/list traversal, no index needed for-each
Index needed or step ≠ +1 for
Condition-dependent repetition, may run 0 times while
Must execute at least once (e.g., menu prompt) do-while
Infinite service loop while(true) + break

When `switch` vs `if-else`?

  • switch: single variable with multiple concrete values, enum routing, state machines
  • if-else: boolean expressions, range checks (x > 10), combining multiple variables
  • switch expression (Java 14+): when the branch produces a value
  • Pattern matching switch (Java 21+): type checking + casting in one construct

Practical Selection Checklist

Use this quick mental model:

  1. Are you routing on one value? Prefer switch.
  2. Are you checking ranges or multiple conditions? Prefer if-else.
  3. Do you need an index? Use classic for.
  4. Do you only need traversal? Use for-each.
  5. Do you need at least one execution? Use do-while.
  6. Is the control flow becoming clever? Extract a method and simplify.

4. Code Examples

4.1 Classic Switch — ❌ Fall-through Bug

// ❌ BAD — missing break causes fall-through
switch (day) {
    case MONDAY:
        System.out.println("Monday");
        // missing break!
    case TUESDAY:
        System.out.println("Tuesday"); // also prints for MONDAY!
        break;
    default:
        System.out.println("Other day");
}
// ✅ GOOD — explicit break in every branch
switch (day) {
    case MONDAY:
        System.out.println("Monday");
        break;
    case TUESDAY:
        System.out.println("Tuesday");
        break;
    default:
        System.out.println("Other day");
}

4.2 Switch Expression (Java 14+)

// ✅ Modern switch expression — no fall-through, returns a value
String type = switch (statusCode) {
    case 200, 201 -> "Success";
    case 301, 302 -> "Redirect";
    case 404      -> "Not Found";
    case 500      -> "Server Error";
    default       -> "Unknown";
};

4.3 Switch Expression with `yield`

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    default -> {
        String s = day.toString();
        int result = s.length();
        yield result; // yield returns the block's value
    }
};

4.4 Pattern Matching in Switch (Java 21+)

// ✅ Type checking + casting combined in switch
static String format(Object obj) {
    return switch (obj) {
        case Integer i -> String.format("int: %d", i);
        case Long l    -> String.format("long: %d", l);
        case String s  -> String.format("String: %s", s);
        case null      -> "null";
        default        -> obj.toString();
    };
}

4.5 Guarded Pattern (Java 21+)

static String classify(Shape shape) {
    return switch (shape) {
        case Circle c when c.radius() > 100   -> "large circle";
        case Circle c                          -> "small circle";
        case Rectangle r when r.area() > 1000  -> "large rectangle";
        case Rectangle r                       -> "small rectangle";
        default                                -> "unknown";
    };
}

4.6 Labeled Break — Exiting Nested Loops

// ✅ Labeled break to exit the outer loop
outer:
for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        if (matrix[i][j] == target) {
            System.out.println("Found at [" + i + "][" + j + "]");
            break outer; // exits the OUTER loop
        }
    }
}

4.7 Off-by-One Error

// ❌ BAD — ArrayIndexOutOfBoundsException
for (int i = 0; i <= arr.length; i++) { // should be < not <=
    System.out.println(arr[i]);
}

// ✅ GOOD
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

4.8 Input Validation with `do-while`

// ✅ At least one execution guaranteed
int value;
do {
    System.out.print("Enter a positive number: ");
    value = scanner.nextInt();
} while (value <= 0);

4.9 `break` vs `continue`

// break — exits the loop entirely
for (int i = 0; i < 10; i++) {
    if (i == 5) break;       // loop ends, i = 5
    System.out.println(i);   // prints 0, 1, 2, 3, 4
}

// continue — skips to next iteration
for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) continue; // skip even numbers
    System.out.println(i);    // prints 1, 3, 5, 7, 9
}

4.10 Search Pattern with Early Return

static int indexOf(int[] values, int target) {
    for (int index = 0; index < values.length; index++) {
        if (values[index] == target) {
            return index;
        }
    }
    return -1;
}

This is often clearer than keeping a mutable foundIndex variable outside the loop.

4.11 Labeled `continue` for Multi-Level Iteration

outer:
for (String row : rows) {
    for (String cell : row.split(",")) {
        if (cell.isBlank()) {
            continue outer;
        }
    }
    System.out.println("Valid row: " + row);
}

Use this sparingly. It is valid, but if it starts to hide business rules, extracting helper methods is usually the cleaner design.


5. Trade-offs

Aspect Details
Readability for-each > for > while for collection traversal. switch expression > if-else chain when 4+ branches.
Performance The JIT compiler optimizes most loops to equivalent native code. for-each on an array compiles to the same bytecode as an indexed for.
switch vs if-else switch on integers and Strings generates tableswitch/lookupswitch bytecode — faster than an if-else chain for many branches.
Fall-through Classic switch fall-through can be useful for intentional grouping, but is error-prone. Switch expressions eliminate fall-through entirely.
Labeled break Powerful for nested loops, but overuse hurts readability — consider refactoring into a method instead.

6. Common Mistakes

Mistake Description Prevention
Missing break Fall-through in classic switch Prefer switch expressions where possible
Off-by-one Using <= instead of < in array indexing Prefer for-each when index is not needed
Infinite loop The condition never becomes false Verify that loop state is updated every iteration
Misusing short-circuit logic Hiding side effects inside && / || conditions Keep conditions declarative; move mutations outside
Unmodified loop variable Forgetting to increment inside while Prefer for when a counter drives iteration
Null in switch NullPointerException before Java 21 in many switch scenarios Defend against null explicitly or use case null -> in modern Java

7. Deep Dive

7.1 Switch Expressions are Exhaustive

Since Java 14, switch expressions must be exhaustive — every possible value must be covered, or the code won't compile. For enums, this means handling all constants or providing a default branch.

7.2 Sealed Classes + Switch (Java 17/21)

sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double r) implements Shape {}
record Rectangle(double w, double h) implements Shape {}
record Triangle(double a, double b, double c) implements Shape {}

// Exhaustive — no default needed when all permitted classes are covered
double area(Shape shape) {
    return switch (shape) {
        case Circle c    -> Math.PI * c.r() * c.r();
        case Rectangle r -> r.w() * r.h();
        case Triangle t  -> {
            double s = (t.a() + t.b() + t.c()) / 2;
            yield Math.sqrt(s * (s - t.a()) * (s - t.b()) * (s - t.c()));
        }
    };
}

7.3 JIT Optimizations on Loops

  • Loop unrolling: the JIT unrolls small fixed-size loops
  • Loop vectorization: uses SIMD instructions where possible
  • Bounds check elimination: if the JIT can prove the index is always valid, it removes array bounds checks
  • Loop inversion: transforms while into do-while for better branch prediction

7.4 At the Bytecode Level

  • tableswitch: for contiguous value ranges (O(1) lookup)
  • lookupswitch: for sparse values (O(log n) binary search)
  • String switch: hashCode()-based lookupswitch + equals() verification

7.5 Structured Control Flow Ages Better

Junior code often optimizes for “fewest lines”, while senior code optimizes for “fewest surprises”. Control structures are a big part of that.

  • a short if can be clearer than a nested ternary
  • an extracted method can be clearer than labeled control transfer
  • an early return can be clearer than deeply nested blocks

Good control flow reduces the amount of state a reader must hold in their head.

7.6 Loops vs Higher-Level Abstractions

Not every loop should remain a loop forever. Once the basics are clear, many traversal tasks become better expressed using streams, collectors, or dedicated library abstractions.

Still, classic loops remain essential when:

  • you need explicit index control
  • you need the lowest overhead on hot code paths
  • you need complex early exit behavior
  • the imperative version is simply easier to understand

8. Interview Questions

When is `switch` a better choice than `if-else`?

switch is usually better when one expression determines the branch and the possible values are concrete and finite, such as enums, status codes, or command names. if-else is better for ranges, compound conditions, and unrelated predicates.

What is the difference between `for-each` and `for`?

for-each is best for full traversal when you do not need the index. Classic for is better when you need index access, custom step sizes, reverse traversal, or coordinated iteration across multiple structures.

Why are switch expressions considered safer than classic switch statements?

Because they eliminate accidental fall-through, encourage exhaustive handling, and are designed to produce a value cleanly. That usually leads to clearer and less error-prone branching logic.

Is `while(true)` always a code smell?

No. It is acceptable when the exit conditions are explicit and easy to reason about, for example in event loops or retry loops. It becomes a smell when termination depends on hidden state or when interruption and shutdown are not handled carefully.


9. Glossary

Term Definition
Control structure A construct that directs the order of statement execution
Conditional branch if/else, ternary — choosing paths based on conditions
Loop Repeated execution of a block
Pre-test loop while — condition checked before each iteration
Post-test loop do-while — condition checked after each iteration
Labeled break Exiting an outer loop by label
Fall-through Execution falling into the next case in classic switch
Switch expression A value-returning switch (Java 14+)
Pattern matching Type-checking + destructuring in one step
Exhaustive Complete coverage — every case is handled
Guarded pattern A pattern with a when condition
Yield Keyword that returns a value from a switch expression block

10. Quick Reference

  • if/else — boolean conditions, 1–3 branches
  • switch (classic) — one variable's concrete values, watch out for break
  • switch expression (Java 14+)-> syntax, no fall-through, returns a value
  • Pattern matching switch (Java 21+) — type checks, when guards, case null
  • for — known iteration count, index needed
  • for-each — collection/array traversal, no index
  • while — 0 or more repetitions, condition first
  • do-while — at least 1 execution, condition last
  • break — exit loop/switch
  • continue — skip to next iteration
  • return — exit method
  • yield — return a value from a switch expression block
  • Labeled break/continue — multi-level loop control
  • Sealed classes + switch — exhaustive pattern matching, no default needed
  • JIT: tableswitch = O(1), lookupswitch = O(log n)

🎮 Games

10 questions