Why combine operators
Real conditions are rarely a single comparison. You might need "this flag is set, or the value is in a certain range". Scala lets you mix && (AND), || (OR) and ! (NOT) in one expression. The result is still one Boolean, and it is evaluated in a fixed order.
Grouping with parentheses
Parentheses decide which parts are evaluated together. Compare these two expressions, which use the same pieces:
7 >= 10 && (5 == 7 || 9 < 12) // false
(7 >= 10 && 5 == 7) || 9 < 12 // true
In the first line the program reads condition1 && condition2, where condition1 is 7 >= 10 (false) and condition2 is (5 == 7 || 9 < 12) (true, because 9 < 12 is true). AND needs both sides to be true, so the result is false.
In the second line the program reads condition1 || condition2. Now condition1 is (7 >= 10 && 5 == 7) (false) and condition2 is 9 < 12 (true). OR needs only one true side, so the result is true. The same four comparisons give opposite answers, purely because of where the parentheses sit.
Precedence without parentheses
When there are no parentheses, Scala applies a fixed order: comparisons first, then &&, then ||. In other words, && binds tighter than ||, as multiplication binds tighter than addition.
println(true || false && false) // true || (false && false)
println((true || false) && false) // false
true
false
The first line is true because false && false is evaluated first, giving false, and then true || false gives true. Adding parentheses in the second line changes the order and the answer.
You can rely on this order, but it is kind to the reader to add parentheses anyway when AND and OR meet in one condition. They cost nothing at run time and make the intended grouping obvious.
Evaluation stops early
Both && and || evaluate left to right and stop as soon as the answer is known. false && x is false and true || x is true, whatever x is, so x is not evaluated. This is useful for guarding a check: in list.nonEmpty && list.head > 0, the call to head only happens when the list has an element.
The lesson's exercise
The code starts with these values:
val specialStudent: Boolean = false
val age: Int = 23
A student is let in if they are a special student, or if their age is between 18 and 30, both ends included.
- Write the age check. "Between 18 and 30" needs two comparisons joined by AND:
age >= 18 && age <= 30. Both bounds are inclusive, so the comparisons use>=and<=. - Join it with the special-student check. Put
specialStudenton the left of||and the parenthesised age check on the right. The parentheses are not required, but they show the grouping. Print"Let him in!"when the condition holds. - Add the
else. It prints"Not allowed"for everyone who fails both checks.
val specialStudent: Boolean = false
val age: Int = 23
if (specialStudent || (age >= 18 && age <= 30)) {
println("Let him in!")
} else {
println("Not allowed")
}
With the starting values, specialStudent is false, but age >= 18 && age <= 30 is true for 23, so the program prints Let him in!. With age = 40 both sides are false and it prints Not allowed. With specialStudent = true and age = 40 the left side alone is enough, and it prints Let him in!.
Common mistakes
- Writing a range as one chain.
18 <= age <= 30does not compile in Scala. Use two comparisons joined by&&. - Using OR for a range.
age >= 18 || age <= 30is true for every age, since each number is at least 18 or at most 30. - Missing parentheses around a mixed condition. Without them,
a || b && cmeansa || (b && c), which may not be what you intended. - Off-by-one bounds. Using
>and<would reject ages 18 and 30.
Key takeaways
- Comparisons are evaluated first, then
&&, then||. - Parentheses override that order, and the same pieces can give opposite results.
- Check a range with
low <= x && x <= high. - Both
&&and||stop evaluating as soon as the result is known.
