JAVA / BRANCHING AND SWITCH EXPRESSIONS
else if chains for mutually exclusive paths
Chain mutually exclusive conditions with else if so exactly one branch runs, ordering tests by specificity and closing with a plain else.
What you will learn
- Read else if as an if inside the previous else: the first true test wins
- Drop bounds the branch above already guarantees, like && score < 90
- Order overlapping tests narrowest first so the broad one cannot steal the case
- Finish with a plain else when a variable must be assigned on every path
Understanding else if chains for mutually exclusive paths
Java has no else if keyword. Writing else if (cond) puts a complete new if statement inside the else branch of the previous one, so a chain of five tests is really five ifs nested five deep, just formatted flat so it reads as a list. That nesting is the whole point: condition number k is only evaluated when conditions 1 through k-1 all produced false, which makes the branches mutually exclusive by construction rather than by careful wording of each test.
Because control only reaches a branch when everything above it failed, each branch inherits the negation of the earlier conditions for free. A grade chain that already tested score >= 90 does not need score >= 80 && score < 90 next, because the < 90 part is guaranteed by position. Restating it is worse than noise: when the A threshold later moves to 93, the duplicated bound quietly disagrees with the real one and scores 90 to 92 fall into a gap that matches nothing. The corollary is that order carries meaning, so overlapping tests are perfectly fine as long as the narrower one comes first.
The final else decides whether the chain runs at most one branch or exactly one. Stop at the last else if and an input matching nothing is a silent no-op, which is also why javac refuses to treat such a chain as total: declare String label; and assign it in every arm of a chain that ends in else if (t >= 30), and you get "variable label might not have been initialized" even though you can prove by hand that the arms cover every int. The compiler reasons about the shape of the statement, not about arithmetic, so a plain else is the only way to promise full coverage.
This is why the chain is the natural shape for decisions built from ranges or priorities, where each case is defined partly by what the cases above it already excluded.
public class GradeChain {
static String grade(int score) {
if (score >= 90) {
return "A";
} else if (score >= 80) {
return "B";
} else if (score >= 70) {
return "C";
} else {
return "F";
}
}
// Same thresholds, wrong order: the loosest test is reached first.
static String ascending(int score) {
if (score >= 70) {
return "C";
} else if (score >= 80) {
return "B";
} else if (score >= 90) {
return "A";
} else {
return "F";
}
}
public static void main(String[] args) {
for (int s : new int[] {95, 83, 71, 40}) {
System.out.println(s + ": grade=" + grade(s) + " ascending=" + ascending(s));
}
}
}else if is just an if nested in the previous else, so the first true condition wins and every later branch silently carries the negation of the ones above it.
Worked examples
A chain is not a list of ifs
The same three conditions produce different results depending on whether they are joined by else.
public class ChainVsSequence {
public static void main(String[] args) {
int n = 12;
int chained = 0;
if (n % 2 == 0) {
chained++;
} else if (n % 3 == 0) {
chained++;
} else if (n % 4 == 0) {
chained++;
}
int separate = 0;
if (n % 2 == 0) {
separate++;
}
if (n % 3 == 0) {
separate++;
}
if (n % 4 == 0) {
separate++;
}
System.out.println("chained = " + chained);
System.out.println("separate = " + separate);
}
}Example explained
Line 1n % 2 == 0 is true for 12, so both else if tests are never evaluated and chained stops at 1.
Line 2The three separate ifs are independent statements, so 12 satisfies all of them and separate reaches 3.
Line 3Identical conditions and identical input, different answer: else is what turns a list of tests into a single decision.
The final else proves the variable is assigned
A chain that initialises a local variable needs a plain else, and needs no redundant lower bounds.
public class ClassifyTemp {
public static void main(String[] args) {
for (int t : new int[] {-5, 3, 21, 38}) {
String label;
if (t < 0) {
label = "freezing";
} else if (t < 10) {
label = "cold";
} else if (t < 30) {
label = "mild";
} else {
label = "hot";
}
System.out.println(t + "C is " + label);
}
}
}Example explained
Line 1String label; has no initial value, so javac must prove that every path through the chain assigns it before the println.
Line 2t < 10 needs no && t >= 0, because that branch is only reachable when t < 0 already evaluated to false.
Line 3Replace the last else with else if (t >= 30) and compilation fails with "variable label might not have been initialized".
Line 4label is declared inside the loop, so each iteration starts with a fresh unassigned variable and the same check applies again.
Priority order in the leap year rule
Three overlapping divisibility tests give the right answer only because the most specific one is first.
public class LeapYear {
static boolean isLeap(int year) {
if (year % 400 == 0) {
return true;
} else if (year % 100 == 0) {
return false;
} else if (year % 4 == 0) {
return true;
} else {
return false;
}
}
public static void main(String[] args) {
for (int y : new int[] {2000, 1900, 2024, 2023}) {
System.out.println(y + " leap? " + isLeap(y));
}
}
}Example explained
Line 12000 satisfies all three conditions; the chain returns at the first one, so the % 400 rule wins.
Line 2Reaching year % 100 == 0 already implies the year is not divisible by 400, which is precisely the century exception.
Line 3Move the % 4 test to the top and 1900 reports true, because the broadest test now matches first.
Line 4The trailing else covers every year not divisible by 4 and is what lets the method compile without a missing return statement.
Important notes
javac only rejects statements that are unreachable in the grammatical sense, such as code after a return; a condition that is merely impossible, like else if (n % 21 == 0) placed after if (n % 7 == 0), compiles cleanly and just never fires.
Evaluation stops at the first true test, so conditions lower in the chain may never run at all; keep method calls with side effects out of them or the behaviour will vary with the input.
Common mistakes
Sorting thresholds ascending (score >= 70 first, score >= 90 last), so the loosest test swallows every high score and the A branch never runs, with no compiler warning.
Using three separate if statements when the cases overlap, so an input matching two tests takes both branches and the effect, such as a discount or a counter, is applied twice.
Ending with else if (t >= 30) instead of else when initialising a variable: either the code will not compile because the variable might not have been initialized, or a field silently keeps its old value.
Try it yourself
Change, predict, then run
Write String httpClass(int code) as one else-if chain that returns "unknown" for codes below 100, then "informational", "success", "redirect", "client error" and "server error", with a final else returning "unknown" again. Use a single comparison per branch, no &&, and print the result for 99, 100, 204, 301, 404, 503 and 700.
Open the Java workspaceCheck your understanding
With int n = 21, the chain runs: if (n % 7 == 0) r = "seven"; else if (n % 3 == 0) r = "three"; else if (n % 21 == 0) r = "twentyone"; else r = "none"; what happens?
- r holds "seven", and the code compiles
- r holds "twentyone", because the most specific matching condition wins
- r holds "three", because 21 is divisible by 3
- It does not compile, because the third condition can never be true
Show answer
21 % 7 is 0, so the first branch assigns "seven" and the remaining conditions are never evaluated; a chain takes the first true test, it does not search for the best match. "twentyone" assumes specificity is detected automatically, but in a chain specificity only matters if you write the narrow test first. And a condition that cannot be true is a logic bug, not unreachable code, so javac accepts it without complaint.