In which our programs stop being calculators and start being decision-makers — and we discover that Pascal's explicitness about branching is, once again, a gift rather than a burden.
Learning Objectives
- Write IF..THEN, IF..THEN..ELSE, and nested IF statements
- Use the CASE statement for multi-way branching
- Construct compound Boolean expressions with AND, OR, NOT
- Apply short-circuit evaluation and understand its implications
- Choose between IF chains and CASE statements for readability and efficiency
In This Chapter
- Opening Narrative: The Fork in the Road
- 5.1 The IF..THEN Statement
- 5.2 IF..THEN..ELSE: Two-Way Branching
- 5.3 Nested IF Statements and IF..ELSE IF Chains
- 5.4 Boolean Expressions in Depth
- 5.5 Short-Circuit Evaluation
- 5.6 The CASE Statement
- 5.7 Common Decision-Making Patterns
- 5.8 Debugging Conditional Logic
- 5.9 Project Checkpoint: PennyWise Menu System
- 5.10 Coming From Other Languages: A Conditional Syntax Comparison
- 5.11 Chapter Summary
Chapter 5: Making Decisions — IF, CASE, and Boolean Logic
In which our programs stop being calculators and start being decision-makers — and we discover that Pascal's explicitness about branching is, once again, a gift rather than a burden.
Opening Narrative: The Fork in the Road
Until now, every program we have written has been a straight line. Data flows in, operations happen in sequence, results flow out. That is the nature of Chapters 3 and 4: you declare variables, you read input, you compute, you display output. Every statement executes, every time, in order.
Real programs do not work that way.
Real programs make decisions. When Rosa Martinelli opens PennyWise to log an expense, the program must decide: is this amount valid? Is the category recognized? Has she exceeded her monthly budget? When Tomas Vieira types a menu choice, the program must decide which action to perform — and what to do if his choice is not on the menu at all.
Decision-making is what separates a calculator from a program. And Pascal, true to its philosophy of clarity, gives us decision constructs that are explicit, readable, and hard to misuse. Where C hides its branching behind cryptic ternary operators and fall-through switches, Pascal gives us if..then..else and case..of — constructs so clear that you can read them aloud and understand what happens.
This chapter is where your programs come alive. Let us begin with the simplest decision a program can make.
🔗 Spaced Review — From Chapter 3: What is the difference between
:=and=in Pascal? The assignment operator:=stores a value in a variable. The equality operator=tests whether two values are equal — and it is the equality operator that drives every decision in this chapter.🔗 Spaced Review — From Chapter 1: Name two advantages of Pascal's strong typing. (1) The compiler catches type mismatches before your program runs. (2) Your code documents its own intentions through explicit type declarations. Strong typing will matter in this chapter because Boolean expressions in Pascal evaluate to the
Booleantype — not to an integer, not to "truthy" or "falsy," but toTrueorFalse, period.
5.1 The IF..THEN Statement
The if..then statement is the most fundamental decision construct in Pascal. It says: if this condition is true, then execute this statement. If the condition is false, the statement is skipped and execution continues with whatever comes next.
5.1.1 Basic Syntax
Here is the formal syntax:
if <boolean-expression> then
<statement>;
And here is a concrete example:
program OverBudgetWarning;
var
amount: Real;
begin
Write('Enter expense amount: $');
ReadLn(amount);
if amount > 100.00 then
WriteLn('WARNING: This expense exceeds $100.00!');
WriteLn('Expense recorded.');
end.
Let us trace through this program with two different inputs.
Input: 45.00. The condition amount > 100.00 evaluates to False. The WriteLn warning is skipped. The program prints only Expense recorded.
Input: 250.00. The condition evaluates to True. The program prints the warning, then prints Expense recorded.
Notice that Expense recorded. prints in both cases. It is not part of the if statement — it is the next statement after the if, and it always executes. This is a crucial distinction that trips up beginners: only the single statement immediately after then is conditional.
5.1.2 The Compound Statement: BEGIN..END Blocks
What if you need more than one statement to execute conditionally? You wrap them in begin..end:
if amount > 100.00 then
begin
WriteLn('WARNING: This expense exceeds $100.00!');
WriteLn('Consider splitting this into multiple categories.');
WriteLn('Budget remaining after this expense: $', (budget - amount):0:2);
end;
Without the begin..end, only the first WriteLn would be conditional. The other two would execute every time — a bug that is maddeningly difficult to spot because the indentation looks correct even when the logic is wrong.
⚠️ Common Pitfall: The Missing BEGIN..END. If you intend to execute multiple statements conditionally but forget the
begin..end, only the first statement is affected by theif. The rest execute unconditionally. Pascal's compiler will not warn you because the code is syntactically valid — just logically wrong. When in doubt, usebegin..end.💡 Theme 1 — Pascal Teaches Programming Right. In C, the same mistake exists with missing braces. But Pascal's
begin..endkeywords are words, not punctuation. They are harder to overlook in code review than a missing{. This is by design. Wirth wanted the structure of a program to be visible in its text, andbegin..endblocks make branching structure impossible to miss.
5.1.3 Rosa's Budget Check
Let us see Rosa Martinelli put this to use. She wants PennyWise to warn her whenever a single expense exceeds a threshold:
program RosaBudgetCheck;
const
WARNING_THRESHOLD = 50.00;
var
expenseAmount: Real;
category: String;
begin
Write('Category: ');
ReadLn(category);
Write('Amount: $');
ReadLn(expenseAmount);
if expenseAmount > WARNING_THRESHOLD then
begin
WriteLn;
WriteLn('*** BUDGET ALERT ***');
WriteLn('This ', category, ' expense of $', expenseAmount:0:2,
' exceeds your $', WARNING_THRESHOLD:0:2, ' threshold.');
WriteLn('Are you sure you want to record it?');
end;
WriteLn('Expense logged: ', category, ' — $', expenseAmount:0:2);
end.
Notice the use of a named constant WARNING_THRESHOLD. This is the kind of discipline Pascal encourages: the magic number 50.00 appears once, with a name that explains its purpose. If Rosa decides to change her threshold to $75, she changes one line.
5.1.4 💡 Try It Yourself
Before reading on, predict the output of this program for the input 35:
program Predict;
var
x: Integer;
begin
ReadLn(x);
if x > 10 then
WriteLn('A');
WriteLn('B');
if x > 30 then
WriteLn('C');
WriteLn('D');
end.
Write your prediction, then compile and run it. Were you right? If not, identify which WriteLn calls are inside if blocks and which are unconditional.
The answer: for input 35, all four letters print (A B C D). The key insight is that WriteLn('B') and WriteLn('D') are not inside any if block — they always execute.
5.2 IF..THEN..ELSE: Two-Way Branching
The if..then statement handles the case where you want to do something or nothing. But often you want to do either this or that. That is what if..then..else provides:
if <boolean-expression> then
<statement-1>
else
<statement-2>;
Exactly one of the two statements executes. If the condition is True, statement-1 executes. If False, statement-2 executes. Never both. Never neither.
program PassFail;
var
score: Integer;
begin
Write('Enter score: ');
ReadLn(score);
if score >= 60 then
WriteLn('PASS')
else
WriteLn('FAIL');
end.
5.2.1 The Critical Rule: No Semicolon Before ELSE
This is the single most common syntax error that beginners make with if..then..else, and it deserves its own subsection because it will bite you repeatedly until the rule becomes instinct.
In Pascal, the semicolon is a statement separator, not a statement terminator. The if..then..else is one single statement. The semicolon between then and else would tell the compiler that the if statement has ended — and then else appears with no matching if, causing a compiler error.
{ WRONG — compiler error! }
if score >= 60 then
WriteLn('PASS'); { <-- This semicolon ends the IF statement }
else { <-- 'else' has no matching 'if' }
WriteLn('FAIL');
{ CORRECT }
if score >= 60 then
WriteLn('PASS') { <-- No semicolon here }
else
WriteLn('FAIL');
⚠️ Common Pitfall: The Semicolon Before ELSE. If you see the error message
Fatal: Syntax error, ";" expected but "ELSE" found— or simplyError: Misplaced "else"— the first thing to check is whether you put a semicolon beforeelse. Remove it. Pascal's semicolon rule is strict, andif..then..elseis where that strictness demands your attention.
When using begin..end blocks with else, the same rule applies — no semicolon after end when else follows:
if amount > 0 then
begin
WriteLn('Positive');
total := total + amount;
end { <-- No semicolon before else }
else
begin
WriteLn('Non-positive — skipping');
errorCount := errorCount + 1;
end; { <-- Semicolon here is fine — this ends the entire if statement }
5.2.2 The Dangling Else Problem
Consider this code:
if a > 0 then
if b > 0 then
WriteLn('Both positive')
else
WriteLn('Which condition does this else belong to?');
Which if does the else match? In Pascal, the rule is unambiguous: else always matches the nearest unmatched if. So the else belongs to if b > 0, not to if a > 0.
If you want the else to match the outer if, you must use begin..end to close the inner if explicitly:
if a > 0 then
begin
if b > 0 then
WriteLn('Both positive');
end
else
WriteLn('a is not positive');
The begin..end block terminates the inner if statement, so the else has no choice but to match the outer if.
💡 Theme 1 — Pascal Teaches Programming Right. The dangling else problem exists in C, C++, and Java too. But Pascal's
begin..endblocks make the solution explicit and readable. You do not need to memorize arcane rules about brace matching — you writebeginandend, and the structure is visible. Wirth understood that programmers make mistakes, and a language should make those mistakes visible rather than hiding them in ambiguous syntax.
5.2.3 Tomas Categorizes His Expenses
Tomas wants to classify his expenses by size:
program ExpenseSize;
var
amount: Real;
begin
Write('Expense amount: $');
ReadLn(amount);
if amount < 0 then
WriteLn('Error: amount cannot be negative.')
else if amount = 0 then
WriteLn('Zero expense — nothing to record.')
else if amount < 10.00 then
WriteLn('Small expense.')
else if amount < 50.00 then
WriteLn('Medium expense.')
else if amount < 200.00 then
WriteLn('Large expense.')
else
WriteLn('Major expense — review your budget!');
end.
This is the if..else if chain pattern. Each condition is tested in order, and only the first matching branch executes. The final else catches everything that did not match any previous condition.
🔗 Coming From Python? This is equivalent to Python's
if..elif..elsechain. The logic is identical; Pascal simply spells outelse ifas two keywords rather than fusing them intoelif.🔗 Coming From C/Java? The pattern is the same as
if..else if..else. The difference is that Pascal usesthenandbegin..endrather than braces and semicolons.
5.3 Nested IF Statements and IF..ELSE IF Chains
5.3.1 Nesting: IFs Inside IFs
Nesting occurs when one if statement appears inside another. This is perfectly legal and sometimes necessary, but deep nesting makes code hard to read and maintain.
program NestedExample;
var
age: Integer;
hasID: Boolean;
isMember: Boolean;
begin
Write('Age: ');
ReadLn(age);
hasID := True; { Simplified for this example }
isMember := True;
if age >= 18 then
begin
if hasID then
begin
if isMember then
WriteLn('Full access granted.')
else
WriteLn('Limited access — members get more features.');
end
else
WriteLn('Please present a valid ID.');
end
else
WriteLn('Sorry, you must be 18 or older.');
end.
This works, but three levels of nesting are already pushing readability. We can flatten this with compound Boolean expressions (Section 5.4) or guard clauses (Section 5.7).
5.3.2 Nested IF with Complex Conditions
Real-world decisions are rarely as simple as the examples above. Let us examine a more realistic scenario: Rosa wants PennyWise to apply different tax rules based on expense category and amount.
program TaxCalculation;
var
amount: Real;
category: String;
taxRate: Real;
isBusiness: Boolean;
begin
Write('Category: ');
ReadLn(category);
Write('Amount: $');
ReadLn(amount);
Write('Business expense? (true/false): ');
ReadLn(isBusiness);
taxRate := 0.0; { Default: no tax }
if isBusiness then
begin
if (category = 'Food') or (category = 'Transport') then
begin
if amount > 75.00 then
taxRate := 0.10 { 10% for large business meals/travel }
else
taxRate := 0.05; { 5% for small business meals/travel }
end
else if category = 'Equipment' then
begin
if amount > 500.00 then
taxRate := 0.0 { Capital expense — handled separately }
else
taxRate := 0.08; { 8% for small equipment }
end
else
taxRate := 0.07; { 7% for other business expenses }
end
else
begin
if (category = 'Food') or (category = 'Housing') then
taxRate := 0.0 { No tax on personal food/housing }
else if amount > 100.00 then
taxRate := 0.06 { 6% for large personal purchases }
else
taxRate := 0.04; { 4% for small personal purchases }
end;
WriteLn('Tax rate: ', (taxRate * 100):0:1, '%');
WriteLn('Tax amount: $', (amount * taxRate):0:2);
WriteLn('Total: $', (amount * (1 + taxRate)):0:2);
end.
This example has three levels of nesting, with compound conditions at the second level. Let us trace through it for a specific input: category = 'Food', amount = 120.00, isBusiness = True.
isBusinessisTrue— enter the first branch.(category = 'Food') or (category = 'Transport')isTrue(becausecategory = 'Food'isTrue) — enter the nested branch.amount > 75.00isTrue(because120 > 75) —taxRate := 0.10.- Output: Tax rate 10.0%, Tax $12.00, Total $132.00.
Now trace with category = 'Equipment', amount = 200.00, isBusiness = True:
isBusinessisTrue.(category = 'Food') or (category = 'Transport')isFalse.category = 'Equipment'isTrue.amount > 500.00isFalse(because200 <= 500) —taxRate := 0.08.- Output: Tax rate 8.0%, Tax $16.00, Total $216.00.
⚠️ Readability Warning: This code works, but three levels of nesting with compound conditions at each level is approaching the limit of comfortable readability. In Chapter 7, we will extract the inner logic into separate procedures (
CalculateBusinessTaxandCalculatePersonalTax), making the top level a clean two-way split. For now, practice tracing through nested conditions — the skill will serve you in every language.
5.3.3 When to Nest vs. When to Chain
Use nesting when the inner decision only makes sense after the outer condition is true. In the example above, checking for an ID only matters if the user is 18+.
Use an else-if chain when you are choosing among mutually exclusive alternatives. Tomas's expense classifier from Section 5.2.3 is a perfect example: the expense is small, medium, large, or major — never two of those at once.
A rule of thumb: if your nesting goes deeper than three levels, step back and look for a way to flatten it. Deep nesting is a code smell in any language, and Pascal gives you several tools to fix it: compound Boolean expressions, case statements, and (in later chapters) procedures.
5.3.3 Execution Tracing: Following the Branches
Let us trace through a nested if with specific values. This skill — mentally executing code — is fundamental to debugging.
var
x, y: Integer;
begin
x := 15;
y := 8;
if x > 10 then { True: 15 > 10 — enter this branch }
begin
if y > 10 then { False: 8 > 10 — skip to else }
WriteLn('A')
else
WriteLn('B'); { This executes: output is 'B' }
WriteLn('C'); { This is inside the outer begin..end, so it executes: output is 'C' }
end
else
WriteLn('D'); { Skipped because outer condition was True }
WriteLn('E'); { Always executes: output is 'E' }
end.
Output: B, C, E (each on its own line).
The key to tracing is identifying which begin..end blocks are active and which statements fall inside vs. outside them. Draw boxes around each begin..end block on paper if it helps. Many experienced programmers still do this when debugging complex branching logic.
💡 Try It Yourself. Trace the same code with
x := 5andy := 20. What prints? (Answer:D,E.)
5.4 Boolean Expressions in Depth
Every if and case decision rests on a Boolean expression — an expression that evaluates to True or False. In Chapter 3, we met the Boolean type and the relational operators (=, <>, <, >, <=, >=). Now we combine them.
5.4.1 Compound Conditions: AND, OR, NOT
Pascal provides three Boolean operators:
| Operator | Meaning | Result is True when... |
|---|---|---|
and |
Logical conjunction | Both operands are True |
or |
Logical disjunction | At least one operand is True |
not |
Logical negation | The operand is False |
Here is a simple example:
if (age >= 18) and (hasTicket = True) then
WriteLn('Welcome to the concert!');
Both conditions must be True for the message to print. If the person is 17 or does not have a ticket, the message is skipped.
And with or:
if (day = 'Saturday') or (day = 'Sunday') then
WriteLn('It is the weekend!');
Either condition being True is sufficient.
And with not:
if not fileExists then
WriteLn('File not found.');
5.4.2 The Precedence Trap
This is where beginners get burned. Consider:
{ WRONG — does not do what you think! }
if x = 1 or x = 2 then
WriteLn('x is 1 or 2');
Pascal's operator precedence evaluates this as:
if x = (1 or x) = 2 then ...
Because or has higher precedence than =. The expression 1 or x performs a bitwise OR on the integers, producing a result that is then compared to 2. This is almost certainly not what you intended.
The fix: always parenthesize.
{ CORRECT }
if (x = 1) or (x = 2) then
WriteLn('x is 1 or 2');
⚠️ Common Pitfall: Missing Parentheses in Boolean Expressions. In Pascal, the logical operators
and,or, andnothave higher precedence than the relational operators=,<>,<,>,<=,>=. This is the opposite of what most beginners expect. Always parenthesize each relational comparison when combining them withandoror. This is not optional — without parentheses, the code either will not compile or will produce wrong results.
Here is the complete precedence table for the operators you need in Boolean expressions:
| Priority | Operators | Category |
|---|---|---|
| Highest | not |
Unary negation |
*, /, div, mod, and |
Multiplicative / logical AND | |
+, -, or, xor |
Additive / logical OR | |
| Lowest | =, <>, <, >, <=, >=, in |
Relational |
The key surprise: and is at the same level as *, and or is at the same level as +. This comes from Pascal's roots in mathematical logic (AND is like multiplication, OR is like addition), but it means you must parenthesize comparisons.
5.4.3 Truth Tables and Truth Table Generation
For reference, here are the truth tables for and, or, and not:
AND Truth Table:
| A | B | A and B |
|---|---|---|
| False | False | False |
| False | True | False |
| True | False | False |
| True | True | True |
OR Truth Table:
| A | B | A or B |
|---|---|---|
| False | False | False |
| False | True | True |
| True | False | True |
| True | True | True |
NOT Truth Table:
| A | not A |
|---|---|
| False | True |
| True | False |
5.4.3a Generating Truth Tables Programmatically
One of the best ways to verify your Boolean logic is to write a program that generates truth tables. Here is a simple truth table generator for a three-variable expression:
program TruthTableGenerator;
var
A, B, C: Boolean;
expr: Boolean;
begin
WriteLn(' A B C | (A and B) or (not C)');
WriteLn(' ------ ------ ------ | --------------------');
for A := False to True do
for B := False to True do
for C := False to True do
begin
expr := (A and B) or (not C);
WriteLn(' ', A:5, ' ', B:5, ' ', C:5, ' | ', expr:5);
end;
end.
This generates all eight possible combinations of three Boolean variables and evaluates the expression for each. The output is:
A B C | (A and B) or (not C)
------ ------ ------ | --------------------
FALSE FALSE FALSE | TRUE
FALSE FALSE TRUE | FALSE
FALSE TRUE FALSE | TRUE
FALSE TRUE TRUE | FALSE
TRUE FALSE FALSE | TRUE
TRUE FALSE TRUE | FALSE
TRUE TRUE FALSE | TRUE
TRUE TRUE TRUE | TRUE
Look at the last row: when A, B, and C are all True, the expression is True because A and B evaluates to True. But notice that whenever C is False, the expression is True regardless of A and B — because not C is True, and True or anything is True under short-circuit evaluation.
Writing truth table generators is not merely an academic exercise. When you encounter a complex Boolean condition in real code and cannot determine whether it is correct by reading it, generate the truth table. The truth table does not lie, and it exposes every combination — including the edge cases you forgot to consider.
💡 Try It Yourself. Modify the truth table generator to evaluate Rosa's expense validation condition:
(amount > 0) and (amount <= 10000) and (not categoryEmpty). Use Boolean variablesamountPos,amountInRange, andcatEmptyas stand-ins. How many of the eight rows produceTrue?
5.4.4 De Morgan's Laws
Augustus De Morgan gave us two laws that let us transform Boolean expressions. They are invaluable for simplifying complex conditions:
- Law 1:
not (A and B)is equivalent to(not A) or (not B) - Law 2:
not (A or B)is equivalent to(not A) and (not B)
In plain English: - "Not (both A and B)" means "either not-A or not-B." - "Not (either A or B)" means "both not-A and not-B."
Practical example. Rosa wants to reject expenses that are not in a valid range:
{ Original — using NOT with AND }
if not ((amount > 0) and (amount <= 10000.00)) then
WriteLn('Invalid amount.');
{ Equivalent — using De Morgan's Law 1 }
if (amount <= 0) or (amount > 10000.00) then
WriteLn('Invalid amount.');
The second version is clearer because it directly states the two invalid conditions. De Morgan's Laws are not just theoretical — they are a refactoring tool for making conditions more readable.
5.4.5 Boolean Variables: Naming Your Conditions
When a Boolean expression gets complex, extract it into a named Boolean variable. This is one of the most powerful readability techniques in all of programming:
var
isValidAmount: Boolean;
isValidCategory: Boolean;
isValidExpense: Boolean;
begin
isValidAmount := (amount > 0) and (amount <= 10000.00);
isValidCategory := (category = 'Food') or (category = 'Transport')
or (category = 'Housing') or (category = 'Other');
isValidExpense := isValidAmount and isValidCategory;
if isValidExpense then
WriteLn('Expense accepted.')
else
WriteLn('Invalid expense — check amount and category.');
end;
Compare this to the alternative:
{ Less readable — everything crammed into one IF }
if ((amount > 0) and (amount <= 10000.00)) and
((category = 'Food') or (category = 'Transport')
or (category = 'Housing') or (category = 'Other')) then
WriteLn('Expense accepted.')
else
WriteLn('Invalid expense — check amount and category.');
Both versions compile to the same machine code. But the first version tells you what is being checked at each step. A Boolean variable is essentially a comment that the compiler can verify.
✅ Best Practice: Name Your Booleans. Whenever a Boolean expression has more than two conditions joined by
and/or, extract it into a named Boolean variable. The variable name documents your intent, and if the condition needs to change later, you change it in one place.
5.5 Short-Circuit Evaluation
5.5.1 What Is Short-Circuit Evaluation?
Short-circuit evaluation means that Pascal can sometimes determine the result of a Boolean expression without evaluating all of its parts.
- For
A and B: ifAisFalse, the result isFalseregardless ofB. Pascal skips evaluatingB. - For
A or B: ifAisTrue, the result isTrueregardless ofB. Pascal skips evaluatingB.
This is not just an optimization — it is a programming technique. Consider:
if (x <> 0) and (100 div x > 10) then
WriteLn('x is a small positive divisor.');
If x is zero, evaluating 100 div x would cause a runtime division-by-zero error. But with short-circuit evaluation, when x <> 0 is False, Pascal never evaluates the div expression. The program is safe.
5.5.2 The {$B} Compiler Directive
Free Pascal supports two Boolean evaluation modes, controlled by the {$B} directive:
{$B-}— Short-circuit evaluation (the default in Free Pascal). Evaluation stops as soon as the result is determined.{$B+}— Complete Boolean evaluation. Both operands are always evaluated, even if the result is already known.
{$B-} { Short-circuit: safe — second part skipped when denominator = 0 }
if (denominator <> 0) and (numerator / denominator > 1.0) then
WriteLn('Ratio exceeds 1.0');
{$B+} { Complete evaluation: DANGEROUS — division by zero occurs! }
if (denominator <> 0) and (numerator / denominator > 1.0) then
WriteLn('Ratio exceeds 1.0');
⚠️ Common Pitfall: Assuming Short-Circuit Evaluation. Free Pascal defaults to
{$B-}` (short-circuit), but Turbo Pascal and some other implementations default to `{$B+}(complete evaluation). If you write guard conditions like(x <> 0) and (y div x > 5), always verify that your compiler is in short-circuit mode. Better yet, add{$B-}explicitly at the top of your program.
5.5.3 When Short-Circuit Evaluation Matters
Short-circuit evaluation is essential for three common patterns:
1. Guard against nil/invalid access:
if (p <> nil) and (p^.value > 10) then { Safe: p^ not accessed if p is nil }
WriteLn('Value found.');
(You will learn about pointers in Chapter 14, but the pattern is worth previewing.)
2. Guard against out-of-range access:
if (index >= 1) and (index <= maxItems) and (items[index] > 0) then
WriteLn('Valid item.');
3. Expensive checks last:
if (age >= 18) and (DatabaseLookup(userID) = 'ACTIVE') then
WriteLn('Access granted.');
If age < 18, the database lookup is skipped entirely — saving time and resources.
🔗 Coming From C/Java? The
&&and||operators in C and Java always short-circuit. Pascal'sandandorshort-circuit only when{$B-}is active. Pascal also providesand thenandor elseoperators (in some modes) that explicitly guarantee short-circuit evaluation, removing any ambiguity.
5.6 The CASE Statement
The if..else if chain works, but when you are choosing among many values of a single expression, the case statement is cleaner, faster, and more expressive.
5.6.1 Basic CASE Syntax
case <selector-expression> of
<label-1>: <statement-1>;
<label-2>: <statement-2>;
...
<label-n>: <statement-n>;
else
<default-statement>;
end;
The selector expression must evaluate to an ordinal type — that is, a type whose values can be counted and ordered: Integer, Char, Boolean, an enumeration, or a subrange. You cannot use Real or String as a case selector. (We will discuss why in a moment.)
Here is a simple example:
program DayOfWeek;
var
day: Integer;
begin
Write('Enter day number (1-7): ');
ReadLn(day);
case day of
1: WriteLn('Monday');
2: WriteLn('Tuesday');
3: WriteLn('Wednesday');
4: WriteLn('Thursday');
5: WriteLn('Friday');
6: WriteLn('Saturday');
7: WriteLn('Sunday');
else
WriteLn('Invalid day number.');
end;
end.
5.6.2 Range Labels and Comma-Separated Labels
Case labels are not limited to single values. You can specify ranges and lists:
case score of
90..100: grade := 'A';
80..89: grade := 'B';
70..79: grade := 'C';
60..69: grade := 'D';
0..59: grade := 'F';
else
WriteLn('Invalid score.');
end;
And comma-separated labels for multiple discrete values:
case ch of
'a', 'e', 'i', 'o', 'u',
'A', 'E', 'I', 'O', 'U':
WriteLn(ch, ' is a vowel.');
'a'..'z', 'A'..'Z':
WriteLn(ch, ' is a consonant.');
else
WriteLn(ch, ' is not a letter.');
end;
Note the order here: vowels are checked first. Once a match is found, the corresponding statement executes and the case is done — there is no fall-through. If we reversed the order, putting 'a'..'z' first, all lowercase letters (including vowels) would match that label, and the vowel label would never be reached.
⚠️ Common Pitfall: Overlapping CASE Labels. Unlike C's
switch, Pascal does not allow overlapping case labels. If you try to list the same value in two different labels, the compiler will report an error. This is a safety feature: it forces you to think carefully about which values belong to which branch.
5.6.3 CASE with BEGIN..END Blocks
When a case label needs to execute multiple statements, wrap them in begin..end:
case menuChoice of
1: begin
WriteLn('=== Add Expense ===');
Write('Category: ');
ReadLn(category);
Write('Amount: $');
ReadLn(amount);
WriteLn('Expense added.');
end;
2: begin
WriteLn('=== View Summary ===');
WriteLn('Total expenses: $', total:0:2);
WriteLn('Number of entries: ', count);
end;
3: WriteLn('Goodbye!');
else
WriteLn('Invalid choice. Please enter 1, 2, or 3.');
end;
5.6.4 Why Ordinal Types Only?
The case statement requires ordinal types because the compiler can build a jump table — an array of addresses indexed by the selector value. When you write case day of 1: ... 2: ... 3: ..., the compiler generates code that jumps directly to the correct branch in constant time, rather than checking each condition sequentially.
This is why Real and String are not allowed: their values are continuous (Real) or arbitrarily long (String), making jump tables impractical. For Real comparisons, use if..else if chains. For String, you can sometimes convert to an enumeration or use the first character in a case combined with if checks.
💡 Theme 5 — Algorithms + Data Structures = Programs. The
casestatement is your first encounter with a key insight: the right data representation enables the right algorithm. Because ordinal types map to integers, the compiler can use a jump table (a data structure) to implement multi-way branching (an algorithm) in O(1) time. Anif..else ifchain with the same logic runs in O(n) time, checking each condition in sequence. Data structure choice drives algorithmic efficiency — even in something as simple as a menu.
5.6.5 CASE with Enumerations (Preview)
Although we will cover enumerations formally in Chapter 12, here is a preview that shows how naturally case works with custom types:
type
TExpenseCategory = (ecFood, ecTransport, ecHousing, ecEntertainment, ecOther);
var
cat: TExpenseCategory;
begin
cat := ecFood;
case cat of
ecFood: WriteLn('Food & Dining');
ecTransport: WriteLn('Transportation');
ecHousing: WriteLn('Rent & Utilities');
ecEntertainment: WriteLn('Entertainment');
ecOther: WriteLn('Other');
end;
end;
No else clause is needed here because we have covered every possible value of TExpenseCategory. The compiler knows this — and in fact, Free Pascal will warn you if you omit a value from the enumeration in your case labels.
5.6.6 The OTHERWISE / ELSE Clause
Free Pascal accepts both else and otherwise as the default clause in a case statement. They are interchangeable:
case x of
1: WriteLn('One');
2: WriteLn('Two');
else { or: otherwise }
WriteLn('Something else');
end;
Standard ISO Pascal uses otherwise, while Borland-derived Pascals (including Free Pascal) support else. We use else in this book for consistency with the Borland tradition that most Free Pascal users follow, but you should recognize otherwise when you encounter it in other codebases.
✅ Best Practice: Always Include an ELSE/OTHERWISE Clause. Even if you believe your case labels cover every possible value, include an
elseclause that handles unexpected input. Defensive programming catches the cases you did not anticipate — and there will always be cases you did not anticipate.
5.7 Common Decision-Making Patterns
Now that we have the tools — if..then, if..then..else, case..of — let us examine the patterns that experienced programmers use to write clean, correct decision logic.
5.7.1 Guard Clauses: Validate Early, Continue Confidently
A guard clause checks for invalid conditions at the top of a block and handles them immediately, so the rest of the code can assume valid data:
{ Without guard clause — deeply nested }
procedure ProcessExpense(amount: Real; category: String);
begin
if amount > 0 then
begin
if amount <= 10000 then
begin
if category <> '' then
begin
{ Actually process the expense }
WriteLn('Processing ', category, ': $', amount:0:2);
end
else
WriteLn('Error: empty category.');
end
else
WriteLn('Error: amount exceeds maximum.');
end
else
WriteLn('Error: amount must be positive.');
end;
{ With guard clauses — flat and readable }
procedure ProcessExpense(amount: Real; category: String);
begin
if amount <= 0 then
begin
WriteLn('Error: amount must be positive.');
Exit; { Leave the procedure immediately }
end;
if amount > 10000 then
begin
WriteLn('Error: amount exceeds maximum.');
Exit;
end;
if category = '' then
begin
WriteLn('Error: empty category.');
Exit;
end;
{ If we reach here, all inputs are valid }
WriteLn('Processing ', category, ': $', amount:0:2);
end;
The guard clause version is flatter, easier to read, and easier to extend. Adding a new validation is one more if..Exit block — no need to restructure the entire nesting.
🔗 Forward Reference: We will formally introduce procedures,
Exit, and parameters in Chapters 7 and 8. For now, understand the pattern: check, reject, move on.
5.7.2 Bounds Checking
Before accessing data by index, always check the bounds:
if (index >= 1) and (index <= MAX_ITEMS) then
WriteLn('Item: ', items[index])
else
WriteLn('Error: index ', index, ' is out of range [1..', MAX_ITEMS, '].');
This pattern will become second nature when we work with arrays in Chapter 9.
5.7.3 Menu Selection Pattern
The menu pattern appears in almost every console application. Here is the general structure:
repeat
WriteLn;
WriteLn('=== MAIN MENU ===');
WriteLn('1. Option A');
WriteLn('2. Option B');
WriteLn('3. Option C');
WriteLn('0. Exit');
WriteLn;
Write('Your choice: ');
ReadLn(choice);
case choice of
1: { Handle option A };
2: { Handle option B };
3: { Handle option C };
0: WriteLn('Goodbye!');
else
WriteLn('Invalid choice. Please try again.');
end;
until choice = 0;
We are previewing the repeat..until loop here (formally covered in Chapter 6), but the decision logic inside the loop — the case statement with an else for invalid input — is a pattern you will use over and over.
5.7.4 State Machines (Preview)
When decisions depend on previous decisions, you are dealing with a state machine. Here is a tiny example — a simple text parser that tracks whether it is inside a quoted string:
var
ch: Char;
inQuote: Boolean;
begin
inQuote := False;
while not EOF do
begin
Read(ch);
if ch = '"' then
inQuote := not inQuote { Toggle state }
else if not inQuote then
Write(ch); { Only output characters outside quotes }
end;
end.
The Boolean variable inQuote is the state. The if statement is the transition logic. This pattern scales up to more complex state machines with enumeration types and case statements — a topic we will revisit in Chapters 10 and 12.
5.7.5 Decision Tables
When you have many conditions and many actions, a decision table can help you organize the logic before you write code. Here is a simple one for Rosa's expense validation:
| Amount > 0? | Amount <= 10000? | Category valid? | Action |
|---|---|---|---|
| No | — | — | Reject: "Amount must be positive" |
| Yes | No | — | Reject: "Amount exceeds maximum" |
| Yes | Yes | No | Reject: "Invalid category" |
| Yes | Yes | Yes | Accept expense |
Each row translates directly to a guard clause or an if..else branch. Decision tables are especially useful when you work with a team — they document the business logic independently of the code.
Let us look at a more complex decision table. Tomas wants PennyWise to suggest a payment method based on the expense amount and category:
| Amount | Category | Has Credit Card? | Suggestion |
|---|---|---|---|
| < 10 | Any | Any | Cash |
| 10-100 | Food/Transport | Yes | Credit card (earns rewards) |
| 10-100 | Food/Transport | No | Debit card |
| 10-100 | Other | Any | Debit card |
| > 100 | Any | Yes | Credit card (buyer protection) |
| > 100 | Any | No | Bank transfer |
Now translate this to Pascal:
function SuggestPayment(amount: Real; category: String;
hasCreditCard: Boolean): String;
begin
if amount < 10.00 then
SuggestPayment := 'Cash'
else if amount <= 100.00 then
begin
if ((category = 'Food') or (category = 'Transport')) and hasCreditCard then
SuggestPayment := 'Credit card (earns rewards)'
else if (category = 'Food') or (category = 'Transport') then
SuggestPayment := 'Debit card'
else
SuggestPayment := 'Debit card';
end
else { amount > 100 }
begin
if hasCreditCard then
SuggestPayment := 'Credit card (buyer protection)'
else
SuggestPayment := 'Bank transfer';
end;
end;
The decision table has six rows; the code has exactly six branches. Every row of the table maps to exactly one code path, and every code path maps to exactly one row. This correspondence is not accidental — it is the whole point. A decision table is a design tool: you build the table first, verify that it covers every case, and then translate it to code.
✅ Best Practice: Build the Table Before the Code. For any decision with more than two conditions, write the decision table first. Number the rows. Then implement each row as a branch. Finally, test with at least one input per row to verify that every branch fires correctly. This systematic approach catches missing cases that ad-hoc coding misses.
5.8 Debugging Conditional Logic
Conditional bugs are among the most frustrating because the program may work correctly for some inputs and fail silently for others. Here are techniques for finding and fixing them.
5.8.1 Tracing Execution with WriteLn
The simplest debugging technique: add WriteLn statements that show which branch is being taken and what the variable values are.
WriteLn('[DEBUG] amount = ', amount:0:2, ', category = "', category, '"');
if amount > 100 then
begin
WriteLn('[DEBUG] Entering amount > 100 branch');
{ ... }
end
else
begin
WriteLn('[DEBUG] Entering amount <= 100 branch');
{ ... }
end;
Remove or comment out the debug lines before delivering your code. (In Chapter 33, we will learn about conditional compilation with {$IFDEF DEBUG}, which lets you leave debug code in place and activate it only when needed.)
5.8.2 Common Boolean Errors
1. Using = instead of := (or vice versa).
{ WRONG — this is a comparison, not an assignment }
if validated = True then ... { Works, but is redundant }
{ Correct and cleaner }
if validated then ... { Boolean variables are already True or False }
{ WRONG — assignment in a condition }
if amount := 50 then ... { Compiler error: := is not a Boolean expression }
Pascal actually protects you here. In C, writing if (x = 5) instead of if (x == 5) is a legal assignment that silently evaluates to 5 (truthy). In Pascal, if x := 5 then is a compiler error because := does not return a value. This is one of Pascal's strongest safety features.
2. Comparing Real values for exact equality.
{ DANGEROUS — floating-point equality is unreliable }
if balance = 0.0 then
WriteLn('Balance is zero.');
{ SAFER — check within a tolerance }
if Abs(balance) < 0.001 then
WriteLn('Balance is effectively zero.');
Floating-point arithmetic can produce tiny rounding errors, so 0.1 + 0.2 may not equal 0.3 exactly. When comparing Real values, use a tolerance (also called epsilon).
3. Off-by-one in boundary conditions.
{ Does this include 100 or not? }
if score >= 90 then grade := 'A'
else if score >= 80 then grade := 'B';
{ What happens when score = 90? It matches the first condition. }
{ What happens when score = 80? It matches the second. }
{ What happens when score = 89? It matches the second condition — wait, is that right? }
Always test your boundaries explicitly. For every if chain, ask: "What happens at the exact boundary value?"
5.8.3 Debugging Example: The Invisible Bug
Consider this expense categorization code that a student wrote. It compiles without errors, produces output for every input, yet gives wrong results for certain values:
program InvisibleBug;
var
amount: Real;
category: String;
begin
Write('Amount: $');
ReadLn(amount);
if amount < 25.00 then
category := 'Small';
if amount < 100.00 then
category := 'Medium';
if amount < 500.00 then
category := 'Large'
else
category := 'Major';
WriteLn('Category: ', category);
end.
Test with amount = 10.00. What do you expect? "Small," right? But the output is "Large." Why?
Walk through it line by line:
1. 10 < 25 is True — category := 'Small'.
2. 10 < 100 is True — category := 'Medium'. (This overwrites "Small"!)
3. 10 < 500 is True — category := 'Large'. (This overwrites "Medium"!)
4. Output: "Large."
The bug is that the student used three separate if statements instead of an if..else if chain. Every condition is checked independently, and the last one that is True wins. The fix is to chain them with else:
if amount < 25.00 then
category := 'Small'
else if amount < 100.00 then
category := 'Medium'
else if amount < 500.00 then
category := 'Large'
else
category := 'Major';
Now only the first matching branch executes. For amount = 10.00, 10 < 25 is True, category becomes "Small," and the rest are skipped.
This is one of the most common conditional logic bugs in every language. The student's code was syntactically correct — the compiler had no objection. But it was logically wrong. Adding [DEBUG] WriteLn statements after each assignment would have revealed the problem instantly: you would see category being overwritten three times.
🔗 Coming From Python/C: This exact bug exists in those languages too. In Python,
if/if/ifchecks all conditions;if/elif/elifstops at the first match. In C, separateifblocks versusif/else ifchains behave identically to Pascal's. The lesson is universal: when conditions are mutually exclusive, chain them withelse.
5.8.4 Testing All Branches
For every decision in your code, you should test with inputs that exercise:
- The true branch — at least one input that makes the condition true.
- The false branch — at least one input that makes the condition false.
- The boundary — inputs at the exact boundary value (e.g., if the condition is
x > 10, test with 10 and 11). - The else clause — inputs that fall through all conditions to the default.
This is called branch coverage, and it is the minimum standard for testing conditional logic. We will formalize testing strategies in later chapters, but the habit starts now.
5.9 Project Checkpoint: PennyWise Menu System
It is time to put everything together. In Chapter 4, we built PennyWise to the point where it could read a single expense (category and amount) and display it with formatted output. Now we add two critical capabilities: a CASE-based menu system and IF-based input validation.
5.9.1 Design
Rosa and Tomas both need the same four operations:
- Add Expense — prompt for category and amount, validate, and store.
- View Expenses — display the last entered expense (we do not have arrays yet — that is Chapter 9).
- View Summary — show the running total and expense count.
- Exit — leave the program with a confirmation.
The menu uses case for dispatch. Validation uses if to reject invalid input. Here is the structure:
loop
display menu
read choice
case choice of
1: add expense (with IF validation)
2: view last expense
3: view summary
4: exit (with confirmation)
else: invalid choice
end
until user exits
5.9.2 The Implementation
Here is the complete PennyWise checkpoint for Chapter 5. Study it carefully — every concept from this chapter appears somewhere in this code.
program PennyWise;
{ PennyWise Personal Finance Manager — Chapter 5 Checkpoint }
{ CASE-based menu system with IF validation }
const
MAX_AMOUNT = 10000.00;
BUDGET_WARNING = 100.00;
var
menuChoice: Integer;
lastCategory: String;
lastAmount: Real;
totalExpenses: Real;
expenseCount: Integer;
confirmExit: Char;
hasExpense: Boolean;
begin
{ Initialize }
totalExpenses := 0.0;
expenseCount := 0;
hasExpense := False;
WriteLn('========================================');
WriteLn(' PennyWise — Personal Finance Manager');
WriteLn(' Version 0.4 (Chapter 5)');
WriteLn('========================================');
repeat
WriteLn;
WriteLn('--- Main Menu ---');
WriteLn('1. Add Expense');
WriteLn('2. View Last Expense');
WriteLn('3. View Summary');
WriteLn('4. Exit');
WriteLn;
Write('Enter your choice (1-4): ');
ReadLn(menuChoice);
WriteLn;
case menuChoice of
1: begin { Add Expense }
Write('Enter category (Food/Transport/Housing/Entertainment/Other): ');
ReadLn(lastCategory);
{ Category validation using IF }
if (lastCategory <> 'Food') and (lastCategory <> 'Transport') and
(lastCategory <> 'Housing') and (lastCategory <> 'Entertainment') and
(lastCategory <> 'Other') then
begin
WriteLn('Invalid category "', lastCategory, '". Expense not recorded.');
end
else
begin
Write('Enter amount: $');
ReadLn(lastAmount);
{ Amount validation using nested IF }
if lastAmount <= 0 then
WriteLn('Error: Amount must be positive. Expense not recorded.')
else if lastAmount > MAX_AMOUNT then
WriteLn('Error: Amount exceeds maximum ($', MAX_AMOUNT:0:2, '). Expense not recorded.')
else
begin
{ Valid expense }
totalExpenses := totalExpenses + lastAmount;
expenseCount := expenseCount + 1;
hasExpense := True;
WriteLn('Expense recorded: ', lastCategory, ' — $', lastAmount:0:2);
{ Budget warning using IF }
if lastAmount > BUDGET_WARNING then
WriteLn('NOTE: This expense exceeds your $',
BUDGET_WARNING:0:2, ' warning threshold.');
end;
end;
end;
2: begin { View Last Expense }
if hasExpense then
WriteLn('Last expense: ', lastCategory, ' — $', lastAmount:0:2)
else
WriteLn('No expenses recorded yet.');
end;
3: begin { View Summary }
WriteLn('=== Expense Summary ===');
WriteLn('Total expenses: $', totalExpenses:0:2);
WriteLn('Number of entries: ', expenseCount);
if expenseCount > 0 then
WriteLn('Average expense: $', (totalExpenses / expenseCount):0:2)
else
WriteLn('Average expense: N/A');
end;
4: begin { Exit }
Write('Are you sure you want to exit? (Y/N): ');
ReadLn(confirmExit);
if (confirmExit <> 'Y') and (confirmExit <> 'y') then
menuChoice := 0; { Reset so the loop continues }
end;
else
WriteLn('Invalid choice. Please enter a number between 1 and 4.');
end;
until menuChoice = 4;
WriteLn;
WriteLn('Thank you for using PennyWise!');
WriteLn('Final summary: ', expenseCount, ' expenses totaling $', totalExpenses:0:2);
end.
5.9.3 What This Checkpoint Demonstrates
Let us map the code back to the concepts in this chapter:
| Concept | Where in the Code |
|---|---|
if..then (simple) |
Budget warning: if lastAmount > BUDGET_WARNING then |
if..then..else |
Has-expense check: if hasExpense then ... else ... |
if..else if chain |
Amount validation: negative → too large → valid |
Compound Boolean (and) |
Category validation with five conditions |
case..of |
Menu dispatch on menuChoice |
case..else |
Invalid menu choice handler |
begin..end blocks |
Throughout — multiple statements in branches |
| Boolean variable | hasExpense tracks whether any expense has been entered |
| Named constants | MAX_AMOUNT, BUDGET_WARNING |
| Guard pattern | Validation rejects invalid input before processing |
5.9.4 What is Missing (and Coming Next)
PennyWise v0.4 has obvious limitations:
- It only remembers the last expense, not all of them. We need arrays (Chapter 9).
- The category validation is verbose — five comparisons joined by
and. We need sets or enumerations (Chapter 12). - The menu loop uses
repeat..until, which we have used but not formally studied. That is Chapter 6. - The code is all in one big block. We need procedures to break it into manageable pieces. That is Chapter 7.
Each limitation points to the next chapter. This is how real programs grow — you identify what is painful, and you learn the tool that fixes it.
💡 Try It Yourself. Extend PennyWise with a fifth menu option: "5. Set Budget Warning Threshold." Let the user change the warning amount. What validation should you add to the new option?
5.10 Coming From Other Languages: A Conditional Syntax Comparison
If you have experience with Python, C, or Java, this section maps Pascal's conditional syntax to what you already know. Even if you do not, this comparison helps you read code in other languages — a skill you will need sooner than you think.
| Feature | Pascal | C / Java | Python |
|---|---|---|---|
| Simple if | if x > 0 then |
if (x > 0) |
if x > 0: |
| If-else | if x > 0 then ... else ... |
if (x > 0) ... else ... |
if x > 0: ... else: ... |
| Else-if chain | else if (two keywords) |
else if (two keywords) |
elif (one keyword) |
| Block delimiters | begin..end |
{ } |
indentation |
| Multi-way branch | case x of |
switch (x) |
match x: (3.10+) |
| Fall-through | No | Yes (until break) |
No |
| Boolean type | Boolean (True/False) |
bool/int (0 = false) |
bool (True/False) |
| Boolean operators | and, or, not |
&&, \|\|, ! |
and, or, not |
| Semicolon before else | Forbidden | Allowed (ends if) | N/A (no semicolons) |
| Ternary operator | None | x > 0 ? a : b |
a if x > 0 else b |
Three differences deserve special attention:
1. Pascal has no ternary operator. In C, you can write result = (x > 0) ? x : -x; to select a value inline. Pascal requires a full if..then..else statement. This is deliberate — Wirth considered the ternary operator harmful to readability. Use an if..then..else with an assignment on each branch instead.
2. C's switch falls through; Pascal's case does not. In C, each case label falls through to the next unless you write break. This is a notorious source of bugs. Pascal's case executes only the matching branch — no fall-through, no break needed. When Pascal programmers encounter C code for the first time, the fall-through behavior is one of the most surprising differences.
3. Pascal's Boolean operators have higher precedence than comparisons. In C and Java, && and || have lower precedence than <, >, ==, etc., so x > 0 && x < 10 works without parentheses. In Pascal, and and or have higher precedence than > and <, so you must write (x > 0) and (x < 10). Python is similar to Pascal in this respect — and and or have lower precedence, but the comparison operators chain naturally. Getting the precedence wrong is the single most common syntax mistake when moving between languages.
💡 Theme 2 — This Transfers to Other Languages. The concept of conditional logic — if/else, multi-way branching, Boolean algebra, short-circuit evaluation, guard clauses — is identical across all programming languages. Only the syntax differs. If you understand decision-making in Pascal, you understand it everywhere. The comparison table above is a quick reference for translating your knowledge.
5.11 Chapter Summary
This chapter transformed your programs from straight-line calculators into decision-making systems. Here is what you learned:
IF..THEN executes a statement only when a condition is True. For multiple conditional statements, wrap them in begin..end. Without begin..end, only the first statement after then is conditional — a frequent source of bugs.
IF..THEN..ELSE provides two-way branching: one path when the condition is True, another when it is False. The critical syntax rule: no semicolon before else. The dangling else always attaches to the nearest if, and begin..end blocks can override this.
IF..ELSE IF chains handle multiple mutually exclusive conditions. They are evaluated top to bottom, and only the first matching branch executes.
Boolean operators — and, or, not — combine conditions into compound expressions. Because and and or have higher precedence than relational operators, you must always parenthesize comparisons: (x > 0) and (x < 10), never x > 0 and x < 10.
De Morgan's Laws transform not (A and B) into (not A) or (not B) and vice versa — a practical tool for simplifying negated conditions.
Short-circuit evaluation ({$B-}, the Free Pascal default) stops evaluating a Boolean expression as soon as the result is determined. This enables guard patterns like (x <> 0) and (100 div x > 5) that would crash under complete evaluation.
The CASE statement provides multi-way branching on ordinal types. It supports single values, ranges (1..5), comma-separated labels (1, 3, 5), and an else/otherwise clause. The compiler can implement case with a jump table for O(1) dispatch. Use case when selecting among discrete values of a single expression; use if..else if for ranges of non-ordinal types or complex conditions.
Decision patterns — guard clauses, bounds checking, menu selection, state machines, decision tables — are reusable templates for organizing conditional logic. Learn to recognize them, and your code will be clearer from the start.
Debugging conditionals requires testing all branches, all boundaries, and all default cases. Use WriteLn tracing to verify which branch executes for a given input. Pascal's strict separation of := and = eliminates an entire class of assignment-in-condition bugs that plague C programmers.
Looking Ahead
In Chapter 6, we will add the third fundamental control structure: loops. You have already seen repeat..until in the PennyWise menu. Now we will study it formally alongside for..to, for..downto, and while..do. With decisions and loops, your programs will be able to handle any computation — a fact that Alan Turing proved in 1936, decades before Wirth wrote his first line of Pascal.
Key Terms Introduced in This Chapter
| Term | Definition |
|---|---|
if..then |
Conditional statement that executes a statement only when a Boolean expression is True |
if..then..else |
Two-way conditional that executes one of two statements based on a Boolean expression |
case..of |
Multi-way branch that selects a statement based on the value of an ordinal expression |
| Compound statement | A begin..end block that groups multiple statements into one |
| Boolean expression | An expression that evaluates to True or False |
| Short-circuit evaluation | Evaluation strategy that stops as soon as the result is determined |
{$B-} |
Compiler directive enabling short-circuit evaluation in Free Pascal |
| De Morgan's Laws | Rules for distributing not over and and or |
| Guard clause | An early check-and-exit pattern that validates input before processing |
| Decision table | A tabular representation of conditions and their corresponding actions |
| Ordinal type | A type with countable, ordered values (Integer, Char, Boolean, enumeration) |
| Dangling else | The ambiguity of which if an else belongs to in nested conditionals |
| Jump table | A compiler-generated array of addresses enabling O(1) case dispatch |
| Branch coverage | Testing strategy that exercises every branch of every conditional |
💡 Theme 1 — Pascal Teaches Programming Right. This chapter has shown repeatedly that Pascal's design choices —
begin..endinstead of braces,:=versus=, mandatory ordinal types forcase, explicitthenkeyword — eliminate entire categories of bugs that other languages tolerate. The language does not just let you write correct decisions; it guides you toward writing them.💡 Theme 5 — Algorithms + Data Structures = Programs. The
casestatement demonstrated that data representation (ordinal types) enables algorithmic efficiency (jump tables). This principle will grow more powerful as we encounter arrays, records, and trees in later chapters, but it starts here: the way you structure your data determines what your program can do — and how fast it can do it.