> "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
In This Chapter
- 3.1 What Is a Variable?
- 3.2 Pascal's Type System: The Big Five
- 3.3 Declaring Variables: The var Section
- 3.4 Numeric Types in Depth
- 3.5 Characters and Strings
- 3.6 Boolean Logic
- 3.7 Constants: The const Section
- 3.8 Expressions and Operators
- 3.9 Type Compatibility and Type Conversion
- 3.10 Practical Considerations and Common Mistakes
- 3.11 Project Checkpoint: PennyWise Variables
- 3.12 Chapter Summary
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:
- What is the variable's name? (So it can label the box.)
- 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:
- What values the variable can hold (e.g., whole numbers, decimal numbers, text)
- How much memory it occupies (e.g., 2 bytes, 4 bytes, 8 bytes)
- 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.1cannot be represented exactly in binary floating-point. This means0.1 + 0.1 + 0.1may not equal0.3exactly. 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
- What three things does a type determine about a variable?
- Which of the Big Five types would you use to store a person's height in meters?
- Which type would you use to store whether a door is locked?
- Why can't
0.1 + 0.1 + 0.1 = 0.3be 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, andSCOREall 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 thebegin..endblock 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:
Integeris your default for everyday counting. It is the type Pascal's standard library expects in most places.LongIntwhen you need numbers beyond 32,767 (population counts, file sizes, financial totals in cents).Int64for very large numbers (nanosecond timestamps, astronomical calculations, database row IDs).ByteandWordfor memory-constrained situations or when you are working with binary data.
💡 Intuition: Choosing an integer type is like choosing a container. A
Byteis a shot glass (holds 0–255). AnIntegeris a coffee mug. ALongIntis a bucket. AnInt64is a swimming pool. Use the smallest container that comfortably fits your data — but when in doubt,IntegerorLongIntis 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.3problem entirely. We will explore this approach in Case Study 2.
🔄 Check Your Understanding
- What is the maximum value a
Bytecan hold? - What happens if you add 1 to a
Bytevariable that holds 255 (without range checking)? - Why might a financial program store dollar amounts as integers rather than Real values?
- 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, subtractOrd('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 becauseandandorhave higher precedence than comparison operators. Without parentheses,age >= 18 and hasIDwould be parsed asage >= (18 and hasID), which is a type error. Always write(age >= 18) and hasIDor(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
- What is the result of
True and (not False)? - Why must you parenthesize comparisons in
(x > 5) and (x < 10)? - What does
<>mean in Pascal? - 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
constkeyword. 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 aRealvalue, and you cannot store aRealin anIntegervariable without explicit conversion. Pascal catches this at compile time. In Python,7 / 3gives2.3333and7 // 3gives2, 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 bugif (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'sif x == 5:vsif 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
- What is the result of
17 div 5? What about17 mod 5? - Why does
var x: Integer; x := 7 / 3;produce a compile error? - What is the difference between
:=and=in Pascal? - What is the result of
2 + 3 * 4and 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:
Roundin Free Pascal uses banker's rounding (round half to even), not the "always round 0.5 up" rule you learned in grade school. SoRound(2.5)= 2 andRound(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
- What is the difference between
Trunc(3.9)andRound(3.9)? - Why does Pascal allow
var y: Real; y := 5;but notvar x: Integer; x := 5.0;? - What unit must you add to use
StrToInt? - In Pascal,
'5' + 3is 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:
studentAgeis better thansa;totalExpensesis better thant - Boolean names: Start with
is,has,can, orshould—isValid,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
- Name three benefits of using meaningful variable names.
- Why should Boolean variable names start with
is,has, orcan? - What
usesclause is needed forStrToInt? - In Pascal, can you use
divwithRealoperands? 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
Realfor 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,Realwith 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
constfor the program name and version (these won't change during execution) - Used
Realfor the monetary amount (with:0:2formatting for two decimal places) - Used
Stringfor 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:
- Change the expense to something you actually bought today. Adjust the amount, category, description, and date.
- Add a second set of variables (
expenseAmount2,expenseCategory2, etc.) for a second expense. Display both expenses and compute their total. - Add a
dailyBudgetconstant 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.