Functions in Pascal
Learn how to define and call functions and procedures in Pascal, including parameters, scope, recursion, and higher-order functions with Docker-ready examples
Functions are the heart of structured programming, and Pascal was designed from the start to make breaking a program into named, reusable pieces both natural and disciplined. Niklaus Wirth’s vision of clean, readable code depended on giving programmers good tools for decomposition.
Pascal makes an explicit distinction that many later languages blurred: a function returns a value and is used inside an expression, while a procedure performs an action and is called as a statement. This separation reinforces intent — when you read code, you immediately know whether a routine produces a result or just does something.
As a strongly, statically typed language, Pascal requires you to declare the type of every parameter and the return type of every function. The compiler enforces these contracts, catching mismatched arguments before your program ever runs. Pascal also gives you precise control over how arguments are passed — by value (a copy) or by reference (the original).
In this tutorial you’ll learn how to define and call functions and procedures, pass parameters by value and by reference, understand variable scope, write recursive routines, and even pass functions as arguments to other functions.
Functions and Procedures
In Pascal, a function is declared with the function keyword and a return type after the parameter list. A procedure uses the procedure keyword and has no return type. Inside a function, you produce the return value either by assigning to the function’s own name (classic Pascal) or to the special Result identifier (Free Pascal).
Create a file named functions.pas:
program Functions;
{ A function returns a value - assign to the function name }
function Square(n: Integer): Integer;
begin
Square := n * n;
end;
{ Free Pascal also lets you assign to the 'Result' identifier }
function Add(a, b: Integer): Integer;
begin
Result := a + b;
end;
{ A procedure performs an action but returns no value }
procedure Greet(personName: string);
begin
WriteLn('Hello, ', personName, '!');
end;
{ Procedures can take parameters and use local variables }
procedure PrintLine(count: Integer);
var
i: Integer;
begin
for i := 1 to count do
Write('-');
WriteLn;
end;
begin
Greet('Pascal');
PrintLine(20);
WriteLn('5 squared is ', Square(5));
WriteLn('3 + 4 = ', Add(3, 4));
end.
Notice how Square and Add appear inside WriteLn calls — they are expressions that evaluate to a value. Greet and PrintLine stand alone as statements. The a, b: Integer shorthand declares two parameters of the same type in one go.
Parameters: By Value vs By Reference
By default, Pascal passes arguments by value — the routine receives a private copy, so changes inside don’t affect the caller. Prefix a parameter with var to pass it by reference, letting the routine modify the caller’s original variable. This example also shows the difference between local and global scope.
Create a file named scope.pas:
program Scope;
var
counter: Integer; { global variables, visible everywhere below }
value: Integer;
{ Pass by value: 'x' is a copy, so the caller is unaffected }
procedure TryToChange(x: Integer);
begin
x := x * 10;
WriteLn('Inside procedure, x = ', x);
end;
{ Pass by reference: 'var n' modifies the caller's variable directly }
procedure Increment(var n: Integer);
begin
n := n + 1;
end;
{ Routines can read and modify global variables }
procedure ShowCounter;
begin
WriteLn('Global counter = ', counter);
end;
begin
value := 5;
TryToChange(value);
WriteLn('After call, value = ', value); { still 5 - unchanged }
counter := 0;
Increment(counter);
Increment(counter);
ShowCounter; { now 2 }
end.
The variable i inside PrintLine (from the previous example) and x inside TryToChange are local — they exist only while the routine runs and are invisible elsewhere. The counter and value variables are global. Pascal’s scoping rules are lexical: an inner block can see the names declared in enclosing blocks.
Recursion
A function that calls itself is recursive. Recursion is a clean way to express problems that are defined in terms of smaller versions of themselves, such as factorials and the Fibonacci sequence.
Create a file named recursion.pas:
program Recursion;
{ n! = n * (n-1)! with a base case of 1 }
function Factorial(n: Integer): Int64;
begin
if n <= 1 then
Factorial := 1
else
Factorial := n * Factorial(n - 1);
end;
{ Each Fibonacci number is the sum of the previous two }
function Fibonacci(n: Integer): Integer;
begin
if n < 2 then
Fibonacci := n
else
Fibonacci := Fibonacci(n - 1) + Fibonacci(n - 2);
end;
var
i: Integer;
begin
WriteLn('Factorials:');
for i := 1 to 10 do
WriteLn(i, '! = ', Factorial(i));
WriteLn;
Write('Fibonacci sequence: ');
for i := 0 to 10 do
Write(Fibonacci(i), ' ');
WriteLn;
end.
The return type of Factorial is Int64 (a 64-bit integer) because factorials grow quickly and would overflow a regular Integer. Every recursive routine needs a base case — here, n <= 1 and n < 2 — to stop the recursion and avoid running forever.
Higher-Order Functions and Default Parameters
Free Pascal supports default parameter values and lets you treat functions as data by declaring a function type and passing routines as arguments. These features require Object Pascal mode, enabled with the {$mode objfpc} directive.
Create a file named advanced.pas:
program Advanced;
{$mode objfpc}{$H+}
{ A default parameter value is used when the caller omits the argument }
function Power(base: Integer; exponent: Integer = 2): Int64;
var
i: Integer;
begin
Result := 1;
for i := 1 to exponent do
Result := Result * base;
end;
{ A function type describes the signature of a routine }
type
IntFunc = function(x: Integer): Integer;
function Double(x: Integer): Integer;
begin
Result := x * 2;
end;
function Negate(x: Integer): Integer;
begin
Result := -x;
end;
{ A higher-order procedure: it accepts a function as a parameter }
procedure ApplyAndShow(f: IntFunc; value: Integer);
begin
WriteLn('Result: ', f(value));
end;
begin
WriteLn('5 squared (default exponent): ', Power(5));
WriteLn('2 to the 8th: ', Power(2, 8));
ApplyAndShow(@Double, 21);
ApplyAndShow(@Negate, 7);
end.
When calling Power(5), the exponent parameter defaults to 2. The @ operator takes the address of a function so it can be passed as a value — @Double hands the Double function itself to ApplyAndShow, which then calls it through the IntFunc parameter.
Running with Docker
| |
Expected Output
Running functions.pas:
Hello, Pascal!
--------------------
5 squared is 25
3 + 4 = 7
Running scope.pas:
Inside procedure, x = 50
After call, value = 5
Global counter = 2
Running recursion.pas:
Factorials:
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
Fibonacci sequence: 0 1 1 2 3 5 8 13 21 34 55
Running advanced.pas:
5 squared (default exponent): 25
2 to the 8th: 256
Result: 42
Result: -7
Key Concepts
- Functions vs procedures — A
functionreturns a value and is used in expressions; aprocedureperforms an action and is called as a statement. This explicit distinction is a hallmark of Pascal’s clarity. - Returning a value — Assign to the function’s name (classic Pascal) or to the
Resultidentifier (Free Pascal); both work in Free Pascal. - Pass by value vs by reference — Arguments are copied by default. Prefix a parameter with
varto pass it by reference so the routine can modify the caller’s variable. - Scope is lexical — Local variables (declared inside a routine’s
varsection) live only during the call; global variables declared at the program level are visible throughout. - Recursion needs a base case — A recursive routine must include a stopping condition; without it the program recurses forever. Use
Int64when results may exceed the range ofInteger. - Type safety — The compiler checks every argument’s type against the parameter declaration, catching mismatches before the program runs.
- Functions as values — Declare a function type and use the
@operator to pass routines as arguments, enabling higher-order patterns. Default parameter values and function types require{$mode objfpc}.
Running Today
All examples can be run using Docker:
docker pull freepascal/fpc:latest
Comments
Loading comments...
Leave a Comment