Lesson 2 — Conditions and Boundaries

← Module home · ← Lesson 1 — Branches · Lesson 3 — Switch →

In this lesson you will learn

Lesson 1 taught the shape of decisions; this lesson builds the questions inside them.


1. Comparison operators — six ways to ask

A condition is an expression whose value is true or false. The six comparison operators produce exactly that:

Operator Reads as Example Value
== equal to marks == 40 true only when equal
!= not equal to attempts != 0 true when different
< less than age < 18 true when strictly smaller
> greater than marks > 40 true when strictly larger
<= less than or equal to age <= 12 true at the boundary too
>= greater than or equal to marks >= 40 true at the boundary too

Key properties:

Read <= and >= as single words. Writing =< or => is a compile error, not a near miss.

2. && — both must hold (logical AND)

if (age >= 13 && age <= 19)

&& is true only when both sides are true. Truth table, filled in the same direction your program evaluates:

age >= 13 age <= 19 && result
true true true
true false false
false true false
false false false

This is the natural shape of a range: min <= value && value <= max. It reads like maths but is two separate questions joined by AND — C++ has no 13 <= age <= 19 shortcut that does what you mean. That chained form compiles and computes a wrong answer: (13 <= age) is a bool (0 or 1), and 1 <= 19 is always true. So every age passes the “teenager” test. P7 traces this exact disaster.

3. || — at least one must hold (logical OR)

if (day == "sat" || day == "sun")     // pseudocode-ish; real string compare in §6

|| is true when at least one side is true — the shape of a menu (any qualifying value wins):

A B A || B
true true true
true false true
false true true
false false false

Vocabulary trap. In everyday speech “or” can mean either but not both (exclusive or). C++’s || is inclusive: if both sides are true, the result is still true. Requirements usually want inclusive — “members or seniors get a discount” includes a senior member. If a requirement truly wants exactly-one, that’s != on the booleans (see exercise E18).

4. ! — flip it (logical NOT)

! inverts a condition: !(age >= 18) means “not an adult”. Two honest uses:

The De Morgan flip (worth memorising for the mistake gallery): !(A && B) equals !A || !B, and !(A || B) equals !A && !B. “Not both in range” = “one of them is out of range”. You will meet this again when a validation condition refuses to behave.

Precedence: ! binds tighter than comparisons, which bind tighter than &&, which binds tighter than ||. So marks == 40 || marks == 45 needs no parentheses, but parenthesise whenever two different operators mix — age < 18 || age > 65 && hasPass is not what it looks like (see P8). House rule: when in doubt, parenthesise; it never hurts and it always documents intent.

5. Boundary conditions — the > vs >= trap

A boundary is the edge value where a rule flips. “Pass at 40” means 40 itself passes — >= — and 39 fails. Get this wrong and exactly one mark is graded wrong, which is the hardest kind of bug to notice and the easiest kind to test for.

marks >= 40    // 40 passes   (boundary INCLUDED)
marks >  40    // 40 fails    (boundary EXCLUDED)

Three habits that make boundaries safe:

  1. Test both sides plus the edge. For a pass rule at 40: check 39, 40, 41. Three values, ten seconds, bug found or rule confirmed. Every lab in this module has a test table with edge rows for exactly this reason.
  2. Copy the requirement’s word choice literally. “40 or more” → >= 40. “More than 40” → > 40. “Above 40” is ambiguous — ask or state your assumption, a habit from Problem Solving §7.
  3. Watch the direction when you flip. The opposite of >= 40 is < 40, not <= 39 and not > 40. Flipping a comparison while keeping the same boundary value is the classic rewrite bug (D6 hunts it).

Adjacent bands share a boundary — decide once. If 60 ends band C and starts band B, then 60 must belong to exactly one of them: either marks >= 60 starts B (and C is marks < 60), or C is marks <= 59. In an else-if ladder the shared value silently goes to the earlier question — know which band owns each edge and put an edge value in the test table to prove it.

6. Compound conditions in the wild

Real rules stack these patterns. Recognise the shape, don’t reinvent it:

Requirement wording Shape C++
“between 18 and 65 inclusive” AND-range age >= 18 && age <= 65
“outside 0–100” inverted range marks < 0 \|\| marks > 100
“Wednesday or Thursday” OR-menu day == 3 \|\| day == 4
“member AND purchased over 500” AND-gate isMember && total > 500
“student OR (senior AND weekday)” mixed isStudent \|\| (isSenior && isWeekday)
“exactly one of A, B” XOR a != b (when both are bool)

Two constructions that pay rent immediately:

Validation guard — reject bad input before using it:

if (marks < 0 || marks > 100)
{
    std::cout << "Invalid marks: " << marks << '\n';
    return 1;                    // stop; nothing sensible can follow
}
// from here on, marks is guaranteed 0..100 — the ladder below needs no re-checks

This pairs with the fail-clear-ignore input check from I/O Lesson 2 §6: that one guards readable input; this one guards sensible input. Real programs need both.

Classification ladder — with ranges ordered so earlier questions do the range work:

if (temp >= 40)      band = "extreme heat";
else if (temp >= 30) band = "hot";
else if (temp >= 20) band = "warm";
else if (temp >= 10) band = "mild";
else                 band = "cold";

7. Compound-condition mistakes

# Mistake What happens Fix
C1 13 <= age <= 19 chained comparison always true (bool is 0/1) split: age >= 13 && age <= 19
C2 if (day == "sat" \|\| "sun") "sun" is a non-zero pointer → always true repeat the comparison each side
C3 AND where OR is meant (marks >= 40 && marks <= 100 used as “invalid”) rejects nothing / rejects everything test with an in-range and an out-of-range value
C4 boundary direction flipped after “simplifying” exactly the edge cases break re-run the 39/40/41 test
C5 ! applied to the wrong term !age >= 18 flips age first parenthesise: !(age >= 18)
C6 unparenthesised &&/\|\| mix AND wins; logic silently re-grouped parenthesise every mixed pair

Each has a hunting exercise in debugging.md — but predictions P7 and P8 spoil C1/C6, so do those hunts first if you want the surprise.

8. bool variables — name the question

A condition can be stored, named, and reused — which makes complex rules readable:

bool isWeekend  = (day == 6 || day == 7);
bool hasTicket  = (ticketCount > 0);
bool canEnter   = hasTicket && !isBanned;

if (canEnter) { ... }

Named conditions turn a compound rule into vocabulary: the if states what, the definitions state why. They also make dry runs trivial — add a column per bool to your trace table and fill them in order. Keep the names assertive (hasTicket, not ticketCheck) so !hasTicket reads as “has no ticket”, not a double negative.


Recap — you can now

Next: Lesson 3 — Switch and the conditional operator: when the question is “which one of these values?”, and the one-line decision that isn’t an if.