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.0 → classic 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 machinesif-else: boolean expressions, range checks (x > 10), combining multiple variablesswitchexpression (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:
- Are you routing on one value? Prefer
switch. - Are you checking ranges or multiple conditions? Prefer
if-else. - Do you need an index? Use classic
for. - Do you only need traversal? Use
for-each. - Do you need at least one execution? Use
do-while. - 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
whileintodo-whilefor 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()-basedlookupswitch+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
ifcan be clearer than a nested ternary - an extracted method can be clearer than labeled control transfer
- an early
returncan 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 branchesswitch(classic) — one variable's concrete values, watch out forbreakswitchexpression (Java 14+) —->syntax, no fall-through, returns a value- Pattern matching switch (Java 21+) — type checks,
whenguards,case null for— known iteration count, index neededfor-each— collection/array traversal, no indexwhile— 0 or more repetitions, condition firstdo-while— at least 1 execution, condition lastbreak— exit loop/switchcontinue— skip to next iterationreturn— exit methodyield— return a value from a switch expression block- Labeled
break/continue— multi-level loop control - Sealed classes + switch — exhaustive pattern matching, no
defaultneeded - JIT:
tableswitch= O(1),lookupswitch= O(log n)
🎮 Games
10 questions