31 min read

> "A type system is a syntactic method for automatically checking the absence of certain erroneous behaviors by classifying program phrases according to the kinds of values they compute."

Learning Objectives

  • Declare variables using the var section with appropriate Pascal types
  • Distinguish between Integer, Real, Char, String, and Boolean types
  • Write arithmetic and Boolean expressions using Pascal operators
  • Explain why Pascal's strong typing prevents bugs that weakly-typed languages allow
  • Use const declarations and understand when to use constants vs. variables

Chapter 3: Variables, Types, and Expressions: Pascal's Strong Typing is a Feature, Not a Bug

"A type system is a syntactic method for automatically checking the absence of certain erroneous behaviors by classifying program phrases according to the kinds of values they compute." — Benjamin C. Pierce, Types and Programming Languages

In Chapter 2, you wrote your first Pascal program. You told the computer to display text on the screen, and it obeyed. That was exhilarating — and also, if we're being honest, a bit limited. A program that can only print hardcoded text is like a calculator with no buttons: nice to look at, not terribly useful.

What makes programs genuinely powerful is their ability to remember things and compute with them. To add two numbers, you need somewhere to store them. To track a user's name, you need a labeled container that holds text. To decide whether a password is correct, you need to compare values and produce a true-or-false answer.

That's what this chapter is about. We are going to learn how Pascal lets you create named containers called variables, how it insists that every container have a specific type describing what kind of data it holds, and how you combine variables and values into expressions that compute results. Along the way, we will encounter Pascal's most distinctive — and, to newcomers, sometimes frustrating — design decision: its strong type system.

Here is the key idea we want you to walk away with: Pascal's strong typing is not a bureaucratic obstacle. It is a safety net. It catches mistakes at compile time — before your program ever runs — that in other languages would silently produce wrong answers, corrupt data, or crash at 2 a.m. on a Saturday. By the end of this chapter, you will not merely tolerate Pascal's type discipline. You will understand why it exists, and you will start to appreciate it.


3.1 What Is a Variable?

Think of your computer's memory as an enormous wall of post-office boxes — millions of small, numbered compartments. Each compartment can hold a value: a number, a character, a chunk of text. But referring to "box number 4,271,088" is hardly convenient. What you want is a name — a meaningful label you can stick on a box so you can say "put 42 in score" instead of "put 42 in memory address 0x00412510."

A variable is exactly that: a named location in memory that holds a value which can change over time.

In many modern languages, you can create a variable on the fly, anywhere in your code. Python lets you write x = 5 without any advance warning. JavaScript lets you conjure variables mid-function. Pascal does not. Pascal requires you to declare every variable before you use it, in a dedicated section of your program.

This is the declaration-before-use rule, and it is fundamental to Pascal's philosophy. Before the compiler lets you store anything, it wants to know:

  1. What is the variable's name? (So it can label the box.)
  2. What type of data will it hold? (So it knows how big the box needs to be and what operations are legal.)

Here is a simple declaration:

var
  age: Integer;

This tells the compiler: "Reserve a box, label it age, and make it big enough to hold an integer." You cannot store the text 'hello' in age. You cannot store the value 3.14 in age. You can only store whole numbers. The compiler enforces this — not at runtime when things go wrong, but at compile time, before your program ever executes.

💡 Intuition: Think of Pascal's type system like kitchen organization. You have a drawer for knives, a shelf for plates, a bin for spices. Could you technically shove a knife into the spice bin? Sure — but you'd cut yourself reaching in later. Pascal prevents you from putting the knife in the wrong drawer in the first place.

Variables in Memory: A Closer Look

When you declare var score: Integer;, the compiler reserves a specific number of bytes in memory — typically 2 or 4 bytes for an Integer. It creates an internal table mapping the name score to that memory address. When you later write score := 95;, the compiler generates machine instructions that store the binary representation of 95 at that address.

This is what happens conceptually:

Before assignment:
  Memory address 1000: [????????]  ← label: "score" (Integer, 4 bytes)

After "score := 95":
  Memory address 1000: [00000000 00000000 00000000 01011111]  ← 95 in binary

You never need to think about memory addresses directly — that's the whole point of variables. But understanding that a variable is a memory location helps explain why types matter: an Integer needs a different amount of space than a Real, and the binary pattern 01011111 means completely different things depending on whether you interpret it as an integer, a floating-point number, or a character. The type tells the compiler (and the CPU) how to interpret the bits.

Why Declaration-Before-Use Matters

In a language like Python, this code runs without complaint:

total = 100
# ...fifty lines later...
totl = total + 50   # typo! 'totl' instead of 'total'
print(total)        # still prints 100, not 150

Python silently creates a new variable called totl. The original total is never updated. This is a real bug that real programmers encounter constantly, and it can take hours to find.

In Pascal, the equivalent code:

var
  total: Integer;
begin
  total := 100;
  totl := total + 50;  { Compiler ERROR: Identifier not found "totl" }
end.

The compiler catches the typo instantly because totl was never declared. The program refuses to compile. The bug is caught in seconds, not hours.

This is Theme 1 in action: Pascal teaches you the right way. Declaration-before-use feels like extra work. It is extra work — a few seconds of typing that saves hours of debugging.


3.2 Pascal's Type System: The Big Five

Every variable in Pascal has a type. A type determines three things:

  1. What values the variable can hold (e.g., whole numbers, decimal numbers, text)
  2. How much memory it occupies (e.g., 2 bytes, 4 bytes, 8 bytes)
  3. What operations are legal (e.g., you can add integers but not add booleans)

Pascal provides many types, but five are foundational. We call them the Big Five:

Type What It Holds Example Values Memory
Integer Whole numbers -42, 0, 1000 2 or 4 bytes
Real Decimal numbers 3.14, -0.001, 2.0 4–8 bytes
Char A single character 'A', '7', '@' 1 byte
String A sequence of characters 'Hello', 'Pascal' variable
Boolean True or false True, False 1 byte

Let's explore each one.

Integer

An Integer holds a whole number — no decimal point, no fraction. In Free Pascal (32-bit mode), an Integer is typically a 16-bit signed value ranging from -32,768 to 32,767. In 64-bit mode or with {$mode objfpc}, it is often a 32-bit value ranging from roughly -2.1 billion to 2.1 billion.

var
  score: Integer;
  temperature: Integer;
  count: Integer;
begin
  score := 95;
  temperature := -12;
  count := 0;
end.

Use Integer for anything you count: scores, quantities, loop counters, ages.

Real

A Real holds a number with a decimal point. In Free Pascal, Real is typically a 64-bit IEEE 754 double-precision floating-point number, giving you roughly 15–16 significant digits.

var
  price: Real;
  pi: Real;
  rate: Real;
begin
  price := 19.99;
  pi := 3.14159265358979;
  rate := 0.075;
end.

Use Real for measurements, prices, scientific values — anything that might have a fractional part.

⚠️ Common Pitfall: Real numbers are stored as approximations. The value 0.1 cannot be represented exactly in binary floating-point. This means 0.1 + 0.1 + 0.1 may not equal 0.3 exactly. We will revisit this issue when we discuss money in Case Study 2.

Char

A Char holds exactly one character. Characters are enclosed in single quotes:

var
  grade: Char;
  initial: Char;
  symbol: Char;
begin
  grade := 'A';
  initial := 'J';
  symbol := '#';
end.

Under the hood, a Char is stored as a number — its position in the character encoding table (typically ASCII or UTF-8's first 128 characters). The letter 'A' is stored as 65, 'B' as 66, and so on. This will become important when we discuss type conversion.

String

A String holds a sequence of characters — a word, a sentence, a paragraph. Strings are also enclosed in single quotes:

var
  name: String;
  greeting: String;
begin
  name := 'Ada Lovelace';
  greeting := 'Welcome to Pascal!';
end.

Strings are one of the most commonly used types. We will explore them in much greater depth in Section 3.5 and dedicate an entire chapter to string manipulation later in the book.

Boolean

A Boolean holds exactly one of two values: True or False. That's it. No maybes, no unknowns.

var
  isPassing: Boolean;
  hasPermission: Boolean;
begin
  isPassing := True;
  hasPermission := False;
end.

Booleans are the foundation of all decision-making in programs. When your code asks "Is the user logged in?" or "Did the student pass?", the answer is a Boolean. We will use them extensively in Chapter 4 when we cover conditionals.


🔄 Check Your Understanding

  1. What three things does a type determine about a variable?
  2. Which of the Big Five types would you use to store a person's height in meters?
  3. Which type would you use to store whether a door is locked?
  4. Why can't 0.1 + 0.1 + 0.1 = 0.3 be guaranteed with Real numbers?

3.3 Declaring Variables: The var Section

In Pascal, all variable declarations go in a var section that appears between the program header and the begin keyword. Here is the general pattern:

program MyProgram;

var
  { all variable declarations go here }
  x: Integer;
  y: Real;
  name: String;

begin
  { code that uses the variables goes here }
end.

Syntax Rules

A variable declaration has this form:

variableName: TypeName;

The name comes first, then a colon, then the type, then a semicolon. Several rules govern variable names (identifiers):

  • Must start with a letter or underscore
  • Can contain letters, digits, and underscores
  • Cannot be a reserved word (begin, end, var, if, etc.)
  • Are case-insensitive (unlike C, Java, and Python): Score, score, and SCORE all refer to the same variable

Declaring Multiple Variables

If several variables share the same type, you can declare them on one line, separated by commas:

var
  width, height, depth: Real;
  firstName, lastName: String;
  isReady: Boolean;

This is exactly equivalent to:

var
  width: Real;
  height: Real;
  depth: Real;
  firstName: String;
  lastName: String;
  isReady: Boolean;

Use whichever style you find more readable. For closely related variables (like width, height, depth), grouping them makes the relationship clear. For unrelated variables, separate declarations are clearer.

Initialization

In standard Pascal, variables are not automatically initialized. If you declare var x: Integer; and then try to use x before assigning a value, you get whatever garbage happened to be in that memory location. Free Pascal will warn you about this:

Warning: Local variable "x" does not seem to be initialized

Always assign a value to a variable before you use it. In Free Pascal, you can initialize a variable at declaration time using the = sign (note: not :=):

var
  count: Integer = 0;
  name: String = 'Unknown';
  rate: Real = 0.05;

⚠️ Common Pitfall: Initialization at declaration (using =) is a Free Pascal extension, not standard ISO Pascal. If portability to other Pascal compilers matters, initialize variables in the begin..end block using := instead.

A Complete Example

program VariableDemo;
var
  studentName: String;
  score: Integer;
  average: Real;
  grade: Char;
  isPassing: Boolean;

begin
  studentName := 'Maria Santos';
  score := 87;
  average := 85.5;
  grade := 'B';
  isPassing := True;

  WriteLn('Student: ', studentName);
  WriteLn('Score:   ', score);
  WriteLn('Average: ', average:0:1);
  WriteLn('Grade:   ', grade);
  WriteLn('Passing: ', isPassing);
end.

Output:

Student: Maria Santos
Score:   87
Average: 85.5
Grade:   B
Passing: TRUE

Notice the :0:1 after average in the WriteLn call. This is Pascal's format specifier for real numbers: :minimumWidth:decimalPlaces. Without it, Real values print in scientific notation (8.55000000000000E+001), which is rarely what you want. We will use format specifiers frequently.


3.4 Numeric Types in Depth

The Integer and Real types we introduced are the most common, but Free Pascal provides a rich family of numeric types for different situations.

Integer Types

Type Size Range
Byte 1 byte 0 to 255
ShortInt 1 byte -128 to 127
SmallInt 2 bytes -32,768 to 32,767
Word 2 bytes 0 to 65,535
Integer 2 or 4 bytes depends on mode
LongInt 4 bytes -2,147,483,648 to 2,147,483,647
LongWord 4 bytes 0 to 4,294,967,295
Int64 8 bytes approximately -9.2 x 10^18 to 9.2 x 10^18
QWord 8 bytes 0 to approximately 1.8 x 10^19

When to use what:

  • Integer is your default for everyday counting. It is the type Pascal's standard library expects in most places.
  • LongInt when you need numbers beyond 32,767 (population counts, file sizes, financial totals in cents).
  • Int64 for very large numbers (nanosecond timestamps, astronomical calculations, database row IDs).
  • Byte and Word for memory-constrained situations or when you are working with binary data.

💡 Intuition: Choosing an integer type is like choosing a container. A Byte is a shot glass (holds 0–255). An Integer is a coffee mug. A LongInt is a bucket. An Int64 is a swimming pool. Use the smallest container that comfortably fits your data — but when in doubt, Integer or LongInt is fine. Modern computers have plenty of memory.

What Happens When You Overflow?

If you store a value too large for a type, the result wraps around. This is called integer overflow:

var
  b: Byte;
begin
  b := 255;
  b := b + 1;  { b is now 0, not 256! }
  WriteLn(b);  { prints 0 }
end.

The Byte type can only hold 0–255. Adding 1 to 255 wraps around to 0. This is not an error message — it happens silently. Free Pascal has a compiler option {$R+}` (range checking) that will raise a runtime error instead of silently overflowing. We strongly recommend using `{$R+} during development:

program OverflowCheck;
{$R+}  { Enable range checking }
var
  b: Byte;
begin
  b := 255;
  b := b + 1;  { Runtime Error 201: Range check error }
end.

Floating-Point Types

Type Size Significant Digits Range (approximate)
Single 4 bytes 7–8 1.5 x 10^-45 to 3.4 x 10^38
Double 8 bytes 15–16 5.0 x 10^-324 to 1.7 x 10^308
Extended 10 bytes 19–20 3.4 x 10^-4932 to 1.1 x 10^4932
Real 8 bytes 15–16 same as Double

In Free Pascal, Real is an alias for Double by default. Use Real or Double for most purposes. Use Single only when memory is critical (storing millions of values). Use Extended when you need maximum precision (scientific computing, geometric algorithms).

📊 Real-World Application: Financial software often avoids floating-point entirely, storing monetary amounts as integers in cents (or the smallest currency unit). So $19.99 becomes 1999 cents. This avoids the 0.1 + 0.2 ≠ 0.3 problem entirely. We will explore this approach in Case Study 2.


🔄 Check Your Understanding

  1. What is the maximum value a Byte can hold?
  2. What happens if you add 1 to a Byte variable that holds 255 (without range checking)?
  3. Why might a financial program store dollar amounts as integers rather than Real values?
  4. What compiler directive enables range checking in Free Pascal?

3.5 Characters and Strings

The Char Type

A Char is a single character stored as a one-byte numeric code. In the ASCII encoding:

  • 'A' through 'Z' are codes 65–90
  • 'a' through 'z' are codes 97–122
  • '0' through '9' are codes 48–57
  • Space is code 32

Because characters are stored as numbers, you can compare them:

var
  ch: Char;
begin
  ch := 'M';
  if ch >= 'A' then
    WriteLn(ch, ' is at or after A in the alphabet');
  { This comparison works because 'M' (77) >= 'A' (65) }
end.

You can also use the Ord function to get a character's numeric code and the Chr function to convert a number back to a character:

WriteLn(Ord('A'));   { prints 65 }
WriteLn(Chr(65));    { prints A }
WriteLn(Ord('0'));   { prints 48 -- not 0! }

⚠️ Common Pitfall: The character '0' is not the number 0. The character '0' has the ASCII value 48. The character '9' has the ASCII value 57. To convert a digit character to its numeric value, subtract Ord('0'): Ord('7') - Ord('0') gives you the integer 7.

The String Type

A String in Free Pascal is a sequence of characters. There are actually several string types:

ShortString: The classic Pascal string. Maximum 255 characters. The first byte stores the length. You can specify a maximum length: String[50] means a string of up to 50 characters.

var
  shortName: String[20];  { up to 20 characters }
begin
  shortName := 'Ada';
  WriteLn(Length(shortName));  { prints 3 }
end.

AnsiString: A dynamically-allocated string with no practical length limit (up to 2 GB). This is the default String type in Free Pascal's {$H+} mode (which is the default in most configurations).

var
  longText: String;  { AnsiString by default }
begin
  longText := 'This can be as long as you need it to be.';
end.

For this book, we will use String (which means AnsiString) unless we specifically need ShortString. The distinction matters mainly for performance-critical code and interoperability with legacy Pascal code.

String Operations

Strings support concatenation with the + operator:

var
  first, last, full: String;
begin
  first := 'Grace';
  last := 'Hopper';
  full := first + ' ' + last;
  WriteLn(full);  { prints Grace Hopper }
end.

You can access individual characters with bracket notation. Important: Pascal strings are 1-indexed, meaning the first character is at position 1, not 0:

var
  s: String;
begin
  s := 'Pascal';
  WriteLn(s[1]);  { prints P }
  WriteLn(s[2]);  { prints a }
  WriteLn(s[6]);  { prints l }
end.

Coming From Python: Python strings are 0-indexed (s[0] is the first character). Pascal strings are 1-indexed (s[1] is the first character). This will trip you up exactly once. After that, you'll be fine.

Coming From C/Java: C strings are null-terminated arrays of characters. Pascal strings store their length explicitly and do not use a null terminator. This means Pascal can determine the length of a string in O(1) time (just read the stored length), while C requires O(n) time (scan until \0). Pascal strings can also contain null characters as regular data.

The Length function returns the number of characters in a string:

WriteLn(Length('Hello'));    { prints 5 }
WriteLn(Length(''));         { prints 0 — empty string }
WriteLn(Length('Hi there')); { prints 8 — space counts }

3.6 Boolean Logic

The Boolean type holds exactly two values: True and False. While that sounds trivially simple, Boolean logic is the engine that drives every decision your program makes.

Boolean Operators

Pascal provides four Boolean operators:

Operator Meaning Example Result
and Both must be true True and False False
or At least one must be true True or False True
not Inverts the value not True False
xor Exactly one must be true True xor True False

Truth Tables

The truth table for and:

A B A and B
True True True
True False False
False True False
False False False

The truth table for or:

A B A or B
True True True
True False True
False True True
False False False

The truth table for xor (exclusive or):

A B A xor B
True True False
True False True
False True True
False False False

Boolean Expressions from Comparisons

Comparison operators produce Boolean results:

var
  age: Integer;
  canVote: Boolean;
begin
  age := 20;
  canVote := age >= 18;      { True }
  WriteLn(canVote);            { prints TRUE }
  WriteLn(age > 21);           { prints FALSE }
  WriteLn(age = 20);           { prints TRUE — note: single = for comparison }
  WriteLn(age <> 15);          { prints TRUE — <> means "not equal" }
end.

The comparison operators in Pascal:

Operator Meaning
= Equal to
<> Not equal to
< Less than
> Greater than
<= Less than or equal to
>= Greater than or equal to

Combining Conditions

You can combine comparisons using and, or, and not:

var
  age: Integer;
  hasID: Boolean;
begin
  age := 20;
  hasID := True;

  if (age >= 18) and hasID then
    WriteLn('Entry permitted')
  else
    WriteLn('Entry denied');
end.

⚠️ Common Pitfall: In Pascal, you must parenthesize comparisons when combining them with and/or. This is because and and or have higher precedence than comparison operators. Without parentheses, age >= 18 and hasID would be parsed as age >= (18 and hasID), which is a type error. Always write (age >= 18) and hasID or (age >= 18) and (hasID = True).

Short-Circuit Evaluation

Free Pascal supports short-circuit evaluation (also called lazy evaluation) of Boolean expressions. With the {$B-} directive (which is the default), if the first operand of and is False, Pascal does not evaluate the second operand — because the result is guaranteed to be False regardless. Similarly, if the first operand of or is True, Pascal skips the second operand.

This is not just an optimization. It enables a common safety pattern:

if (denominator <> 0) and (numerator / denominator > threshold) then
  WriteLn('Ratio exceeds threshold');

With short-circuit evaluation, if denominator is 0, the division never executes, avoiding a division-by-zero error. Without short-circuit evaluation ({$B+}), both operands would be evaluated, and the program would crash.


🔄 Check Your Understanding

  1. What is the result of True and (not False)?
  2. Why must you parenthesize comparisons in (x > 5) and (x < 10)?
  3. What does <> mean in Pascal?
  4. How does short-circuit evaluation prevent a division-by-zero error?

3.7 Constants: The const Section

Some values in your program should never change. The value of pi is always 3.14159265. The maximum number of students in a class is always 30 (per school policy). Sales tax is 8.25% (until the legislature changes it). For these, Pascal provides constants.

True Constants

A true constant is a value that is fixed at compile time and cannot be modified:

const
  PI = 3.14159265358979;
  MAX_STUDENTS = 30;
  PROGRAM_NAME = 'PennyWise';
  TAX_RATE = 0.0825;
  IS_DEBUG = False;

Notice that true constants use = (not :=) and do not have an explicit type — the compiler infers the type from the value.

Typed Constants

Pascal also supports typed constants, which have an explicit type:

const
  MAX_RETRIES: Integer = 3;
  DEFAULT_NAME: String = 'Guest';
  EPSILON: Real = 0.00001;

⚠️ Warning: In classic Borland Pascal, typed constants are actually initialized variables — their values can be changed at runtime despite the const keyword. Free Pascal follows this behavior by default for backward compatibility. To make typed constants truly constant (immutable), use the {$J-}` directive or `{$WRITEABLECONST OFF}. We recommend this for new code.

When to Use Constants vs. Variables

Use a constant when: - The value is known at compile time and should never change - You want to give a meaningful name to a "magic number" - You need to use the same value in multiple places (change it once, changes everywhere)

Use a variable when: - The value will be computed or read from input at runtime - The value changes during program execution

Bad practice — magic numbers:

{ What do these numbers mean? }
if score >= 60 then
  WriteLn('Passed');
area := 3.14159 * radius * radius;

Good practice — named constants:

const
  PASSING_SCORE = 60;
  PI = 3.14159265358979;

{ Now the code documents itself }
if score >= PASSING_SCORE then
  WriteLn('Passed');
area := PI * radius * radius;

Named constants make your code self-documenting. When someone reads PASSING_SCORE, they immediately understand the intent. When they read 60, they have to guess.

💡 Intuition: Constants are promises. When you write const TAX_RATE = 0.0825, you are telling every reader of your code: "This value is fixed. You do not need to track where it might change. You can trust it." That promise makes the code dramatically easier to reason about.


3.8 Expressions and Operators

An expression is a combination of values, variables, operators, and function calls that produces a result. Expressions are the verbs of programming — they do things.

Arithmetic Operators

Operator Meaning Example Result
+ Addition 7 + 3 10
- Subtraction 7 - 3 4
* Multiplication 7 * 3 21
/ Real division 7 / 3 2.3333...
div Integer division 7 div 3 2
mod Modulus (remainder) 7 mod 3 1

The Crucial Difference: / vs div

This is one of the most important distinctions in Pascal arithmetic. The / operator always performs real (floating-point) division, even when both operands are integers:

var
  a, b: Integer;
  result: Real;
begin
  a := 7;
  b := 3;
  result := a / b;       { result = 2.3333... (Real division) }
  WriteLn(result:0:4);   { prints 2.3333 }
end.

The div operator performs integer division — it divides and discards the fractional part:

var
  a, b, result: Integer;
begin
  a := 7;
  b := 3;
  result := a div b;   { result = 2 (truncated toward zero) }
  WriteLn(result);      { prints 2 }
end.

⚠️ Common Pitfall: If you write var x: Integer; x := 7 / 3;, you get a compile error because / returns a Real value, and you cannot store a Real in an Integer variable without explicit conversion. Pascal catches this at compile time. In Python, 7 / 3 gives 2.3333 and 7 // 3 gives 2, but Python won't stop you from accidentally using the wrong one. Pascal forces you to be explicit about your intent.

The mod operator gives the remainder after integer division:

WriteLn(7 mod 3);    { prints 1 — because 7 = 2*3 + 1 }
WriteLn(10 mod 5);   { prints 0 — 10 divides evenly by 5 }
WriteLn(13 mod 4);   { prints 1 — because 13 = 3*4 + 1 }

The mod operator is incredibly useful for cyclic patterns: determining if a number is even (n mod 2 = 0), cycling through days of the week (day mod 7), or extracting digits (n mod 10 gives the last digit).

The Assignment Operator: :=

In Pascal, assignment uses the colon-equals symbol :=, not a single equals sign. This is deliberate: it prevents confusion between assignment and comparison.

x := 5;     { Assignment: "x becomes 5" }
if x = 5    { Comparison: "is x equal to 5?" }

Coming From C/Java: C uses = for assignment and == for comparison. This leads to the infamous bug if (x = 5) which assigns 5 to x instead of comparing. Pascal's := vs = distinction makes this bug structurally impossible. You cannot accidentally assign when you mean to compare.

Coming From Python: Python uses = for assignment and == for comparison. The same risk exists: if x = 5: is a syntax error in Python 3 (but not in Python 2's if x == 5: vs if x = 5:). Pascal's approach is even cleaner — the symbols are visually distinct.

Operator Precedence

When an expression contains multiple operators, Pascal evaluates them in a specific order:

Precedence Operators Category
1 (highest) not Unary
2 *, /, div, mod, and Multiplicative
3 +, -, or, xor Additive
4 (lowest) =, <>, <, >, <=, >= Comparison

Operators at the same precedence level are evaluated left to right.

{ Example: operator precedence }
var
  result: Integer;
begin
  result := 2 + 3 * 4;      { 14, not 20 — multiplication first }
  result := (2 + 3) * 4;    { 20 — parentheses override precedence }
  result := 10 - 4 - 2;     { 4 — left to right: (10-4)-2 }
  result := 10 div 3 mod 2; { 1 — left to right: (10 div 3) mod 2 = 3 mod 2 }
end.

When in doubt, use parentheses. They cost nothing and make your intent crystal clear.


🔄 Check Your Understanding

  1. What is the result of 17 div 5? What about 17 mod 5?
  2. Why does var x: Integer; x := 7 / 3; produce a compile error?
  3. What is the difference between := and = in Pascal?
  4. What is the result of 2 + 3 * 4 and why?

3.9 Type Compatibility and Type Conversion

Pascal's strong typing means you cannot freely mix types. You cannot store a Real in an Integer variable, or a String in a Char variable, without explicit conversion. This is the heart of Pascal's safety — and the reason it catches so many bugs at compile time.

🚪 Threshold Concept: Strong Typing as Safety Net

This is the central insight of this chapter, and it is worth pausing to absorb it fully.

In a weakly typed language, the system silently converts between types, often with surprising results:

// JavaScript — weakly typed
"5" + 3        // "53" (string concatenation, not addition!)
"5" - 3        // 2   (now it's subtraction?!)
true + true    // 2   (booleans become numbers?!)
"" == false    // true (empty string equals false?!)

In a strongly typed language like Pascal, none of these are legal:

{ Pascal — strongly typed }
'5' + 3         { Compile error: Incompatible types }
True + True      { Compile error: Operator not applicable }

At first, this feels restrictive. "Just let me add the string to the number!" you might protest. But consider: if '5' + 3 silently gives you '53' (string concatenation) when you intended 8 (arithmetic addition), you have a bug. And this bug is invisible — the program runs happily, producing wrong answers.

Pascal's compiler says: "I don't know what you mean. Do you want string concatenation or arithmetic addition? Tell me explicitly." And so you write:

result := StrToInt('5') + 3;    { Explicit: convert string to integer, then add }

or:

result := '5' + IntToStr(3);    { Explicit: convert integer to string, then concatenate }

The compiler forces you to state your intent. This is not bureaucracy. This is clarity. Every explicit conversion in your code is a statement of purpose: "I know this is a string, and I deliberately want to treat it as a number." Future readers — including future you — will understand exactly what the code is doing.

📊 Real-World Application: How Strong Typing Prevents Real Bugs

In 1996, the Ariane 5 rocket exploded 37 seconds after launch. The cause? A 64-bit floating-point value was converted to a 16-bit integer, causing an overflow. The value was too large. The conversion crashed the guidance system, which shut down the engine, which triggered self-destruction. Total cost: $370 million. In a strongly typed language with proper range checking, the compiler would have flagged the dangerous conversion at compile time, and engineers would have been forced to handle the overflow explicitly. We explore this further in Case Study 1.

Implicit Type Promotion

Pascal does allow one important implicit conversion: integer to real. Since every integer value is also a valid real value (3 is the same as 3.0), Pascal automatically promotes integers to reals when needed:

var
  x: Integer;
  y: Real;
begin
  x := 5;
  y := x;      { OK: Integer automatically promoted to Real }
  y := x + 2;  { OK: Integer expression promoted to Real }
  y := x / 3;  { OK: / always returns Real anyway }
end.

The reverse is not allowed without explicit conversion:

var
  x: Integer;
  y: Real;
begin
  y := 3.7;
  x := y;     { Compile error: Incompatible types }
end.

Conversion Functions

Pascal provides several functions for explicit type conversion:

Function From To Example
Trunc(x) Real Integer Trunc(3.7) = 3 (truncates toward zero)
Round(x) Real Integer Round(3.7) = 4 (rounds to nearest)
Ord(c) Char Integer Ord('A') = 65
Chr(n) Integer Char Chr(65) = 'A'
IntToStr(n) Integer String IntToStr(42) = '42'
StrToInt(s) String Integer StrToInt('42') = 42
FloatToStr(x) Real String FloatToStr(3.14) = '3.14'
StrToFloat(s) String Real StrToFloat('3.14') = 3.14

Note

IntToStr, StrToInt, FloatToStr, and StrToFloat require the SysUtils unit. Add uses SysUtils; after your program line to use them.

Trunc vs Round

These are used frequently, so let's be precise:

WriteLn(Trunc(3.2));   { 3 — chops off decimal }
WriteLn(Trunc(3.9));   { 3 — chops off decimal }
WriteLn(Trunc(-3.7));  { -3 — truncates toward zero }

WriteLn(Round(3.2));   { 3 — rounds down }
WriteLn(Round(3.5));   { 4 — rounds to nearest even (banker's rounding) }
WriteLn(Round(3.7));   { 4 — rounds up }
WriteLn(Round(4.5));   { 4 — rounds to nearest even (banker's rounding) }

⚠️ Common Pitfall: Round in Free Pascal uses banker's rounding (round half to even), not the "always round 0.5 up" rule you learned in grade school. So Round(2.5) = 2 and Round(3.5) = 4. This is actually more mathematically correct for statistical applications, but it surprises many beginners.

String-to-Number and Number-to-String

Converting between strings and numbers is one of the most common operations, especially when dealing with user input:

program ConversionDemo;
uses SysUtils;
var
  inputStr: String;
  value: Integer;
  price: Real;
begin
  inputStr := '42';
  value := StrToInt(inputStr);
  WriteLn(value * 2);           { prints 84 }

  inputStr := '19.99';
  price := StrToFloat(inputStr);
  WriteLn(price * 1.08:0:2);   { prints 21.59 (with 8% tax) }

  { Converting numbers to strings for concatenation }
  WriteLn('The answer is ' + IntToStr(value));
  WriteLn('Price: $' + FloatToStrF(price, ffFixed, 10, 2));
end.

🐛 Debugging Spotlight: Type Mismatch Errors

The most common error beginners encounter is the type mismatch. Let's look at typical scenarios and their fixes:

Error: Incompatible types: got "Real" expected "Integer"

var x: Integer;
x := 3.5;           { Error! }
x := Trunc(3.5);    { Fix: explicit truncation }
x := Round(3.5);    { Fix: explicit rounding }

Error: Incompatible types: got "String" expected "Integer"

var x: Integer;
x := '42';               { Error! }
x := StrToInt('42');      { Fix: explicit conversion }

Error: Operator is not overloaded: "String" + "Integer"

var name: String; age: Integer;
WriteLn(name + age);              { Error! }
WriteLn(name + IntToStr(age));    { Fix: convert integer to string }

Error: Incompatible types: got "Char" expected "String"

var s: String; c: Char;
c := 'Hello';    { Error! Too many characters for Char }
c := 'H';        { Fix: single character only }

Each of these errors is Pascal saying: "I caught a potential bug for you." Train yourself to read type mismatch errors not as criticism but as assistance.


🔄 Check Your Understanding

  1. What is the difference between Trunc(3.9) and Round(3.9)?
  2. Why does Pascal allow var y: Real; y := 5; but not var x: Integer; x := 5.0;?
  3. What unit must you add to use StrToInt?
  4. In Pascal, '5' + 3 is a compile error. In JavaScript, it produces '53'. Which behavior is safer, and why?

3.10 Practical Considerations and Common Mistakes

Let's consolidate the most important practical lessons from this chapter. These are the mistakes that every Pascal beginner makes at least once. Our goal is to make you aware of them now so that when you encounter the compiler error, you recognize it immediately and know exactly how to fix it.

Mistake 1: Confusing := and =

{ WRONG: using = for assignment }
x = 5;            { This is not valid in a statement context }

{ RIGHT: using := for assignment }
x := 5;

{ RIGHT: using = for comparison }
if x = 5 then ...

The compiler will catch this, but it is worth internalizing: := means "becomes" (assignment). = means "is equal to" (comparison).

Mistake 2: Integer Division When You Want Real Division

var
  average: Real;
  total, count: Integer;
begin
  total := 7;
  count := 3;

  { WRONG: integer operands with div gives integer result }
  average := total div count;   { average = 2.0, not 2.333... }

  { RIGHT: use / for real division }
  average := total / count;     { average = 2.333... }
end.

The / operator always returns a Real result, even with integer operands. Use div only when you intentionally want integer division.

Mistake 3: Uninitialized Variables

var
  total: Integer;
begin
  { total has not been assigned — contains garbage }
  total := total + 10;  { Bug! Adding 10 to garbage }
end.

Always initialize variables before using them. Free Pascal will warn you, but treat the warning as an error.

Mistake 4: String Indexing Starting at 0

var
  s: String;
begin
  s := 'Hello';
  WriteLn(s[0]);   { NOT the first character! This accesses the length byte in ShortString }
  WriteLn(s[1]);   { This is the first character: 'H' }
end.

Pascal strings are 1-indexed. The first character is s[1], not s[0].

Mistake 5: Forgetting Parentheses in Boolean Expressions

{ WRONG: and has higher precedence than >= }
if age >= 18 and age <= 65 then ...
{ Parsed as: if age >= (18 and age) <= 65 then ... → type error }

{ RIGHT: parenthesize comparisons }
if (age >= 18) and (age <= 65) then ...

Mistake 6: Trying to Use div or mod with Real Numbers

var
  x: Real;
begin
  x := 10.5;
  WriteLn(x div 3);   { Compile Error: div requires integer operands }
  WriteLn(x mod 3);   { Compile Error: mod requires integer operands }
end.

The div and mod operators work exclusively with integer types. If you need the integer quotient of a real division, first convert to integer, then use div:

WriteLn(Trunc(x) div 3);  { OK: Trunc converts to integer first }

Or use / and then Trunc the result:

WriteLn(Trunc(x / 3));    { OK: divide as real, then truncate }

Mistake 7: Forgetting the uses Clause for Conversion Functions

program ConvertError;
var
  s: String;
  n: Integer;
begin
  s := '42';
  n := StrToInt(s);  { Compile Error: Identifier not found "StrToInt" }
end.

The functions StrToInt, IntToStr, StrToFloat, and FloatToStr live in the SysUtils unit. You must add uses SysUtils; after the program line:

program ConvertFixed;
uses SysUtils;  { Add this line! }
var
  s: String;
  n: Integer;
begin
  s := '42';
  n := StrToInt(s);  { Now it works }
end.

The built-in conversion functions Ord, Chr, Trunc, and Round do not require any uses clause — they are part of the Pascal language itself.

Naming Conventions

Good variable names make code readable. They are a form of documentation that lives inside your code and never gets out of date. Follow these conventions:

  • Variables: camelCase — studentName, totalScore, isReady
  • Constants: UPPER_SNAKE_CASE — MAX_STUDENTS, TAX_RATE, PI
  • Programs: PascalCase — ExpenseTracker, GradeCalculator
  • Meaningful names: studentAge is better than sa; totalExpenses is better than t
  • Boolean names: Start with is, has, can, or shouldisValid, hasPermission, canVote

Consider these two snippets. They produce identical output, but one is dramatically easier to understand:

{ Hard to read: what do these names mean? }
var
  a: Real;
  b: Real;
  c: Real;
begin
  a := 100.0;
  b := 0.08;
  c := a + (a * b);
  WriteLn(c:0:2);
end.
{ Self-documenting: the names explain the purpose }
var
  subtotal: Real;
  taxRate: Real;
  totalWithTax: Real;
begin
  subtotal := 100.0;
  taxRate := 0.08;
  totalWithTax := subtotal + (subtotal * taxRate);
  WriteLn(totalWithTax:0:2);
end.

Both programs compute $108.00. But the second one tells you *why* it is computing $108.00. When you return to this code in six months, the meaningful names will save you from having to reverse-engineer your own logic.

💡 Theme 6 — Simplicity is Strength: Pascal's strict rules may feel like extra work, but they produce code that is predictable. When you read Pascal code, you know that every variable was intentionally declared, every type was deliberately chosen, and every type conversion was explicitly written. There are no hidden conversions, no implicit coercions, no surprises. This simplicity is a feature.

A Note on Writing Style: Algorithms + Data Structures = Programs

Niklaus Wirth, the creator of Pascal, titled his famous textbook Algorithms + Data Structures = Programs. That title captures a deep truth. A program is not just code that does things — it is a combination of data (the information you work with, stored in variables with specific types) and algorithms (the step-by-step procedures that transform that data).

This chapter has been entirely about the data side: variables, types, constants, and expressions. Starting in Chapter 4, we will add algorithmic structure — decisions (if..then..else) and repetition (loops). The combination of well-chosen data types and clear algorithms is what makes programs powerful. This is Theme 5: Algorithms + Data Structures = Programs.


🔄 Check Your Understanding

  1. Name three benefits of using meaningful variable names.
  2. Why should Boolean variable names start with is, has, or can?
  3. What uses clause is needed for StrToInt?
  4. In Pascal, can you use div with Real operands? Why or why not?

3.11 Project Checkpoint: PennyWise Variables

Let's apply everything we've learned to our running project: PennyWise, the personal expense tracker. In Chapter 2, PennyWise was just a shell — it printed a welcome message. Now we'll give it memory.

The Goal

Define variables to represent a single expense: an amount, a category, a description, and a date. Display the expense in a formatted way.

Thinking Through the Types

Before we write code, let's think about what types to use:

Data Type Choice Reasoning
Expense amount Real Dollars and cents require a decimal point.
Category String Categories are text: "Food", "Transport", etc.
Description String Free-form text describing the expense.
Date String For now, we'll store dates as text. Later chapters will introduce proper date handling.

Design Note: Using Real for money is a simplification. As we discussed in Section 3.4, floating-point arithmetic can introduce tiny rounding errors. In a production application, we would store amounts as integers in cents ($19.99 → 1999). For now, Real with careful formatting is good enough, and we'll revisit this decision in a later chapter.

The Code

program PennyWise;

const
  PROGRAM_NAME = 'PennyWise';
  VERSION = '0.2';

var
  expenseAmount: Real;
  expenseCategory: String;
  expenseDescription: String;
  expenseDate: String;

begin
  { Set up a sample expense }
  expenseAmount := 4.50;
  expenseCategory := 'Food';
  expenseDescription := 'Coffee at campus cafe';
  expenseDate := '2026-03-23';

  { Display the expense }
  WriteLn('=================================');
  WriteLn('  ', PROGRAM_NAME, ' v', VERSION);
  WriteLn('=================================');
  WriteLn;
  WriteLn('Expense Record:');
  WriteLn('  Date:        ', expenseDate);
  WriteLn('  Category:    ', expenseCategory);
  WriteLn('  Description: ', expenseDescription);
  WriteLn('  Amount:      $', expenseAmount:0:2);
  WriteLn;
  WriteLn('=================================');
end.

Output:

=================================
  PennyWise v0.2
=================================

Expense Record:
  Date:        2026-03-23
  Category:    Food
  Description: Coffee at campus cafe
  Amount:      $4.50

=================================

What We Accomplished

  • Used const for the program name and version (these won't change during execution)
  • Used Real for the monetary amount (with :0:2 formatting for two decimal places)
  • Used String for text data
  • Applied meaningful variable names that describe their purpose
  • Structured the output with alignment and visual separators

What's Still Missing

This version of PennyWise has several obvious limitations, and it is worth naming them explicitly because each limitation maps to a Pascal feature we have not yet learned:

Limitation Pascal Feature That Solves It Chapter
Only one hardcoded expense User input with ReadLn Chapter 4
No decision-making if..then..else conditionals Chapter 4
Cannot track multiple expenses for/while loops Chapter 5
Repeated variable groups Arrays Chapter 6
No reusable expense display Procedures and functions Chapter 7
No grouped expense record Records and custom types Chapter 8
Data lost when program ends File I/O Chapter 10

This table illustrates an important principle of learning to program: you do not need to know everything to build something useful. PennyWise v0.2 works. It is limited, but it works. Each chapter will remove a limitation and make PennyWise more capable. By the end of Part II, PennyWise will be a fully functional expense tracker that reads from files, handles multiple expenses, and produces summary reports.

Try It Yourself

Before moving on, try these modifications to the PennyWise checkpoint:

  1. Change the expense to something you actually bought today. Adjust the amount, category, description, and date.
  2. Add a second set of variables (expenseAmount2, expenseCategory2, etc.) for a second expense. Display both expenses and compute their total.
  3. Add a dailyBudget constant and compute how much of the budget remains after the expense. Display a message showing the remaining budget.

These exercises reinforce variable declaration, type selection, constant usage, and formatted output — all the core skills of this chapter.

In the next chapter, we'll add user input so PennyWise can accept expenses interactively instead of using hardcoded values.


3.12 Chapter Summary

We have covered an enormous amount of ground in this chapter. Do not feel you need to memorize every detail — the specifics of which conversion function does what, or the exact range of a Byte, are things you can always look up. What matters is that you understand the concepts: why types exist, how they protect you, and how to think about data.

This chapter introduced the fundamental building blocks of data manipulation in Pascal:

Variables are named memory locations that hold values. Pascal requires you to declare every variable before use, specifying both its name and type. This catches typos and forces intentional design.

Types define what values a variable can hold and what operations are legal. The Big Five types — Integer, Real, Char, String, and Boolean — cover most programming needs. Beyond these, Free Pascal provides a rich family of numeric types for special requirements.

Constants (const) give meaningful names to fixed values, eliminating magic numbers and making code self-documenting.

Expressions combine values, variables, and operators to compute results. Pascal distinguishes between real division (/) and integer division (div), and between assignment (:=) and comparison (=).

Strong typing is Pascal's defining feature. The compiler enforces type compatibility, preventing you from accidentally mixing incompatible types. When you need to convert between types, Pascal provides explicit conversion functions (Trunc, Round, Ord, Chr, StrToInt, IntToStr, etc.) that document your intent.

The central insight of this chapter is the threshold concept: strong typing is a safety net, not a cage. Every type mismatch error the compiler reports is a bug it caught before your program ran. Every explicit type conversion in your code is a statement of intent that makes the code clearer. The discipline Pascal requires is the discipline that produces reliable software.

This discipline transfers. Whether you go on to write Python, Java, Rust, or any other language, the habit of thinking about types — of asking "what kind of data is this?" and "is this conversion safe?" — will make you a better programmer. That is Theme 2: the discipline transfers.

Key Terms

Term Definition
Variable A named memory location that holds a value
Type A classification that determines what values a variable can hold and what operations are legal
Declaration A statement that creates a variable with a name and type
Constant A named value that cannot change during program execution
Expression A combination of values, variables, and operators that produces a result
Strong typing A type system that prevents implicit conversion between incompatible types
Type conversion Explicitly transforming a value from one type to another
Integer overflow When a computation produces a value too large for the variable's type
Assignment operator The := symbol used to store a value in a variable
Comparison operator Operators (=, <>, <, >, <=, >=) that produce Boolean results

What's Next

In Chapter 4, we'll learn how to make decisions with if..then..else and how to read input from the user. PennyWise will become interactive — accepting expenses from the keyboard and deciding whether they exceed a budget threshold. The Boolean logic and comparison operators from this chapter will be essential.