Topic 2.1
Making Decisions with if and else
In one line
An if statement runs a block of code only when a boolean condition is true; else and else if add the other paths. Java checks the conditions top to bottom, runs the first branch that matches, and refuses any condition that isn't a real boolean.
Think of it like this
Before leaving home you look out of the window. If it is raining, you take an umbrella. Otherwise (else), you take sunglasses. You never take both, and you always take exactly one. An if/else in Java is that same choice: look at a fact, then take one path.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Control flow
- The order in which the statements of a program run.
if,switchand loops change that order. - Condition
- An expression that is either
trueorfalse, written in the round brackets afterif. - Block
- A group of statements inside curly braces
{ }. Variables declared inside a block exist only inside it. - Branch
- One of the possible paths through an
if/else. Exactly one branch of an if/else runs. - else-if ladder
- A chain of
if ... else if ... else if ... elsetests, checked from top to bottom. - Nested if
- An
ifstatement placed inside anotheriforelseblock. - Definite assignment
- The compiler rule that a local variable must surely have a value on every path before you read it.
- Guard clause
- An early check at the top of a piece of code that handles a special case (often by returning) so the main logic stays flat.
Step by step
01A one-way decision: plain if
The simplest form runs a block only when the condition is true. When it is false, Java skips the block and carries on with the next statement.
Read if (temperature > 30) aloud as 'if temperature is greater than thirty'. The > is a comparison operator (Topic 1.8) and produces a boolean.
public class Main {
public static void main(String[] args) {
int temperature = 34;
if (temperature > 30) {
System.out.println("It's hot. Drink water.");
}
System.out.println("Have a nice day.");
}
}02A two-way decision: if and else
else is the 'otherwise' path. Exactly one of the two blocks runs, never both and never neither.
Notice that else has no condition of its own. It means 'every case the if didn't catch'.
int age = 15;
if (age >= 18) {
System.out.println("You can vote.");
} else {
System.out.println("Not yet. " + (18 - age) + " years to go.");
}03Many paths: the else-if ladder, and why order matters
To choose among more than two paths, chain else if. Java tests each condition in order and stops at the first true one.
Because it stops at the first match, the order is part of the logic. If you test score >= 60 before score >= 90, a score of 95 matches the first test and gets a D. Always test the narrowest range first, or write ranges that can't overlap.
int score = 82;
String grade;
if (score >= 90) {
grade = "A";
} else if (score >= 80) { // only reached when score < 90
grade = "B";
} else if (score >= 70) { // only reached when score < 80
grade = "C";
} else {
grade = "F";
}
System.out.println(grade); // B04Braces, the one-statement rule, and the dangling else
Without braces, an if controls exactly one statement. Indentation means nothing to the compiler. The second line below always runs, even though it looks like part of the if.
An else pairs with the nearest if that has no else yet. In the second snippet the else belongs to if (raining), not to if (temp > 10), so when temp is 5 nothing prints at all. This is the dangling else problem, and it is the main reason most style guides (Google's included) require braces on every if.
int temp = 5;
boolean raining = false;
if (temp > 30)
System.out.println("Hot!");
System.out.println("Stay inside."); // NOT inside the if: always runs
if (temp > 10)
if (raining)
System.out.println("Take an umbrella.");
else // pairs with if (raining)!
System.out.println("Cold!");05The condition must be a boolean
In C, if (x = 5) assigns 5 to x and then treats 5 as 'true'. That typo has caused countless bugs. Java closes the door: x = 5 has type int, and an int is not a boolean, so the code doesn't compile.
One version of the typo still slips through: when the variable itself is a boolean. if (done = true) assigns true to done and the condition is always true. Write if (done) and if (!done) instead of comparing booleans with ==, and the typo can't happen.
int x = 3;
if (x = 5) { } // compile error: int cannot be converted to boolean
boolean done = false;
if (done = true) { // compiles! assigns true, always runs
System.out.println("Always printed");
}
if (done) { } // the clean way to test a boolean06Definite assignment: the compiler checks every path
Local variables (variables declared inside a method) have no default value. Before you read one, the compiler proves that every possible path assigned it. If any path might skip the assignment, it's a compile error.
Below, when x > 2 is false, s is never assigned, so println(s) is rejected. Adding an else that assigns s fixes it, because now both paths assign. This check is done entirely at compile time and costs nothing at run time.
int x = 5;
String s;
if (x > 2) {
s = "big";
}
System.out.println(s); // error: variable s might not have been initialized
// Fixed: every path assigns s
String t;
if (x > 2) {
t = "big";
} else {
t = "small";
}
System.out.println(t); // big07What the compiler really produces
The JVM has no if keyword. javac turns an if into a conditional jump: compare two values and, if a test holds, continue at another instruction. Look at the bytecode with javap -c (it ships with the JDK).
Notice the flipped test. The source says age >= 18; the bytecode says if_icmplt 9, 'if age is less than 18, jump to instruction 9' (the else-branch). The then-branch is the code that falls straight through. This is why an if is extremely cheap: one compare and one jump.
static String check(int age) {
if (age >= 18) {
return "adult";
} else {
return "minor";
}
}08Flat beats deep: guard clauses
Deeply nested ifs are hard to read: by the fourth level you've forgotten what the first one checked. A guard clause handles the special case first and leaves early, so the main path stays at the left margin.
You'll write guards with return once you meet methods (Phase 3) and with continue inside loops (Topic 2.8). The idea is the same: 'get the odd cases out of the way first'.
// Nested: the real work is buried three levels deep
static String ship(String address, int items, boolean paid) {
if (address != null) {
if (items > 0) {
if (paid) {
return "Shipping " + items + " items";
} else { return "Not paid"; }
} else { return "Empty cart"; }
} else { return "No address"; }
}
// Guard clauses: same behaviour, flat and readable
static String shipFlat(String address, int items, boolean paid) {
if (address == null) return "No address";
if (items <= 0) return "Empty cart";
if (!paid) return "Not paid";
return "Shipping " + items + " items";
}Try it yourself
- 1
Predict, then run the ladder
Open 'Grades with an else-if ladder'. Before running, write down the grade for 89, 90 and 59. Add three
printlnlines for them and run. Were the boundary values (90 exactly, 59) what you expected? - 2
Break the order on purpose
In the grade method, swap the
score >= 90andscore >= 60rungs. Predict which grades change, then run. Only scores of 60 and above are affected, and they all become D. - 3
Remove the braces
In 'The dangling else' example, set
temp = 20andraining = false. Predict both outputs before you run. (Without braces you'll now seeCold!even though it's 20 degrees, because theelsebelongs toif (raining).)
Code & diagrams
`grade` is a small helper method (Phase 3 covers methods). Each call runs the ladder once.
Expected output
95 -> A
80 -> B
79 -> C
60 -> D
12 -> FExpected output
wrong order: D
right order: AExpected output
Without braces:
With braces:
Cold!The `for (int year : years)` loop just repeats the check for each year; Topic 2.7 explains it.
Expected output
1900 is not a leap year
2000 is a leap year
2024 is a leap year
2025 is not a leap year
no name or short namestatic java.lang.String check(int);
Code:
0: iload_0 // push age
1: bipush 18 // push 18
3: if_icmplt 9 // if age < 18 jump to 9 (the else branch)
6: ldc #7 // String adult
8: areturn
9: ldc #9 // String minor
11: areturnBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use = instead of == in a condition
Write if (x = 5) where x is an int.
public class Main {
public static void main(String[] args) {
int x = 3;
if (x = 5) {
System.out.println("five");
}
}
}Break #2
Assign a variable on only one path
Declare String s;, assign it only inside an if without an else, then print it.
public class Main {
public static void main(String[] args) {
int x = 5;
String s;
if (x > 2) { s = "big"; }
System.out.println(s);
}
}Break #3
A stray semicolon after the condition
Put a ; right after if (x > 10).
public class Main {
public static void main(String[] args) {
int x = 5;
if (x > 10);
{
System.out.println("big");
}
}
}Myth vs fact
Myth
Indentation decides which statements belong to an if.
Fact
Only braces do. Without braces an if controls exactly one statement, and an else pairs with the nearest unmatched if.
Myth
if (x) works for numbers, like in C or JavaScript.
Fact
Java conditions must be boolean. if (count) doesn't compile; write if (count != 0).
Myth
Every condition in an else-if ladder is checked.
Fact
Java stops at the first true condition. Later rungs are never evaluated, which is why their order matters.
Myth
A long else-if chain is slow.
Fact
Each rung is one comparison and one jump. A ladder of five is a handful of nanoseconds. Choose if vs switch for readability, not speed.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
javaccompilesif (a >= b)to the inverse jump (if_icmpltto the else-branch) so the then-branch falls through. The JIT later reorders basic blocks using branch profiles, so the hot path ends up as straight-line code regardless of how you wrote it. - ▸
Definite assignment is specified in JLS chapter 16. It is flow analysis on the source, not on values:
if (x > 2) s = "a"; if (x <= 2) s = "b";still fails, because the compiler doesn't reason that the two conditions cover every case. A constant condition is different:if (true) s = "a";passes, because constant expressions are evaluated at compile time. - ▸
if (false) { ... }is deliberately not an 'unreachable statement' error (unlikewhile (false)), so you can writeif (DEBUG)with astatic final boolean DEBUG = false;. The compiler drops the block entirely from the bytecode: Java's form of conditional compilation. - ▸
Branch-heavy code on unpredictable data can be slow on modern CPUs because of branch misprediction (around 10–20 cycles each). The JIT sometimes converts simple
ifs into branch-free conditional moves (cmov). Measure with JMH before hand-optimising.
Remember this
- 1
An
ifstatement has a condition in round brackets and a block in curly braces:if (age >= 18) { ... }. The condition must be an expression of typeboolean(trueorfalse). Java does not treat numbers as truth values the way C does:if (1)andif (count)are compile errors, which rules out a whole family of C bugs. - 2
elsegives the 'otherwise' path.else iflets you test more conditions in a row, forming a ladder. Java checks the conditions from top to bottom and runs only the first branch whose condition is true; the rest are skipped, even if they would also be true. That means the order of the tests matters: put the most specific (narrowest) condition first. - 3
Braces are optional when a branch has a single statement, but then only that one statement belongs to the
if. Anything after it runs every time. Anelsealways pairs with the nearest unmatchedif(the 'dangling else' rule), whatever the indentation suggests. Always using braces removes both traps. - 4
The compiler tracks definite assignment: if you declare a variable and give it a value only inside some branches, Java refuses to let you read it afterwards (
variable s might not have been initialized). Assign it in every branch (anelseis often the missing piece) or give it a starting value. - 5
Conditions are often built with
&&,||and!from Topic 1.8. Because&&and||short-circuit (they stop as soon as the answer is known), you can write a guard:if (name != null && name.length() > 3)never callslength()onnull. - 6
Under the hood, an
ifbecomes a conditional jump in bytecode. The compiler usually flips the test:if (age >= 18)compiles to 'if age < 18, jump over the then-block'. There is no runtime cost toelse ifbeyond one comparison per rung that is actually tested.
Explain it without notes
What happens when two conditions in an else-if ladder are both true?
Explain the dangling else problem and how to avoid it.
Why does if (x = 5) fail to compile in Java when it compiles in C, and which variant of the typo still compiles?
What is definite assignment, and why does the compiler reject reading a variable assigned only inside an if?
Practice
Write a program that classifies a temperature t as freezing (below 0), cold (0–14), mild (15–24) or hot (25 and above). Test it with -5, 0, 20 and 30.
Given three ints a, b, c, print the largest using only if/else (no Math.max). Test with 3, 9, 4.
Print whether a number is positive, negative or zero, and on a second line whether it is even or odd. Test with -7 and 0.
Trade-offs
- ↔
An else-if ladder handles ranges and mixed conditions; a
switch(Topics 2.3 and 2.4) reads better when you compare one value against many fixed constants. - ↔
Guard clauses keep code flat, but many early exits can hide the main path in long methods. Use them for genuine special cases (bad input, nothing to do), not for every branch.
- ↔
Omitting braces saves two characters and invites the dangling-else and 'second line always runs' bugs. Most teams require braces always.
Done when you can
Done when you can write if, if/else and an else-if ladder from memory, with braces.
Done when you can explain why the order of an else-if ladder matters and fix a wrongly ordered one.
Done when you can spot the dangling else and the stray-semicolon bug in someone else's code.
Done when you can read the 'might not have been initialized' error and fix it by covering every path.
Done when you can explain that an if compiles to a conditional jump, and why the test appears inverted in bytecode.