Intermediate

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Pull the official Free Pascal image
docker pull freepascal/fpc:latest

# Compile and run the functions example
docker run --rm -v $(pwd):/app -w /app freepascal/fpc:latest \
    sh -c 'fpc functions.pas && ./functions'

# Run the scope and parameters example
docker run --rm -v $(pwd):/app -w /app freepascal/fpc:latest \
    sh -c 'fpc scope.pas && ./scope'

# Run the recursion example
docker run --rm -v $(pwd):/app -w /app freepascal/fpc:latest \
    sh -c 'fpc recursion.pas && ./recursion'

# Run the higher-order functions example
docker run --rm -v $(pwd):/app -w /app freepascal/fpc:latest \
    sh -c 'fpc advanced.pas && ./advanced'

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 function returns a value and is used in expressions; a procedure performs 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 Result identifier (Free Pascal); both work in Free Pascal.
  • Pass by value vs by reference — Arguments are copied by default. Prefix a parameter with var to pass it by reference so the routine can modify the caller’s variable.
  • Scope is lexical — Local variables (declared inside a routine’s var section) 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 Int64 when results may exceed the range of Integer.
  • 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
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining