32 min read

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

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 Boolean type — not to an integer, not to "truthy" or "falsy," but to True or False, 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 the if. The rest execute unconditionally. Pascal's compiler will not warn you because the code is syntactically valid — just logically wrong. When in doubt, use begin..end.

💡 Theme 1 — Pascal Teaches Programming Right. In C, the same mistake exists with missing braces. But Pascal's begin..end keywords 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, and begin..end blocks 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 simply Error: Misplaced "else" — the first thing to check is whether you put a semicolon before else. Remove it. Pascal's semicolon rule is strict, and if..then..else is 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..end blocks make the solution explicit and readable. You do not need to memorize arcane rules about brace matching — you write begin and end, 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..else chain. The logic is identical; Pascal simply spells out else if as two keywords rather than fusing them into elif.

🔗 Coming From C/Java? The pattern is the same as if..else if..else. The difference is that Pascal uses then and begin..end rather 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.

  1. isBusiness is True — enter the first branch.
  2. (category = 'Food') or (category = 'Transport') is True (because category = 'Food' is True) — enter the nested branch.
  3. amount > 75.00 is True (because 120 > 75) — taxRate := 0.10.
  4. Output: Tax rate 10.0%, Tax $12.00, Total $132.00.

Now trace with category = 'Equipment', amount = 200.00, isBusiness = True:

  1. isBusiness is True.
  2. (category = 'Food') or (category = 'Transport') is False.
  3. category = 'Equipment' is True.
  4. amount > 500.00 is False (because 200 <= 500) — taxRate := 0.08.
  5. 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 (CalculateBusinessTax and CalculatePersonalTax), 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 := 5 and y := 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, and not have higher precedence than the relational operators =, <>, <, >, <=, >=. This is the opposite of what most beginners expect. Always parenthesize each relational comparison when combining them with and or or. 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 variables amountPos, amountInRange, and catEmpty as stand-ins. How many of the eight rows produce True?

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: if A is False, the result is False regardless of B. Pascal skips evaluating B.
  • For A or B: if A is True, the result is True regardless of B. Pascal skips evaluating B.

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's and and or short-circuit only when {$B-} is active. Pascal also provides and then and or else operators (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 case statement 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. An if..else if chain 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 else clause 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 Truecategory := 'Small'. 2. 10 < 100 is Truecategory := 'Medium'. (This overwrites "Small"!) 3. 10 < 500 is Truecategory := '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/if checks all conditions; if/elif/elif stops at the first match. In C, separate if blocks versus if/else if chains behave identically to Pascal's. The lesson is universal: when conditions are mutually exclusive, chain them with else.

5.8.4 Testing All Branches

For every decision in your code, you should test with inputs that exercise:

  1. The true branch — at least one input that makes the condition true.
  2. The false branch — at least one input that makes the condition false.
  3. The boundary — inputs at the exact boundary value (e.g., if the condition is x > 10, test with 10 and 11).
  4. 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:

  1. Add Expense — prompt for category and amount, validate, and store.
  2. View Expenses — display the last entered expense (we do not have arrays yet — that is Chapter 9).
  3. View Summary — show the running total and expense count.
  4. 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 operatorsand, 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..end instead of braces, := versus =, mandatory ordinal types for case, explicit then keyword — 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 case statement 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.