Functions in Delphi
Learn how to define functions and procedures in Delphi (Object Pascal) - parameters, default values, var/const/out modes, scope, recursion, and function references with Docker-ready examples
Functions are how you break a program into reusable, named pieces of logic. Delphi inherits Pascal’s clear distinction between two kinds of routines: a function returns a value, while a procedure performs an action but returns nothing. This separation, dating back to Pascal’s original 1970 design, makes intent obvious at a glance — if a routine gives something back, it’s a function; if it just does something, it’s a procedure.
Delphi is a multi-paradigm, statically and strongly typed language, and its routines reflect that. Every parameter has a declared type, every function declares its return type, and the compiler enforces both. Delphi also adds conveniences that standard Pascal lacks: default parameter values, several parameter-passing modes (var, const, out), and procedural types that let you treat functions as values you can pass around.
In this tutorial you’ll learn how to declare functions and procedures, pass parameters in different modes, give parameters default values, understand local versus global scope (including Delphi’s nested routines), write recursive functions, and use procedural types to build higher-order routines. All examples run with Free Pascal in Delphi compatibility mode.
Functions vs Procedures
The most fundamental distinction in Delphi is between a function (returns a value) and a procedure (does not). Inside a function, the special implicit variable Result holds the value to be returned.
Create a file named functions_basic.dpr:
program FunctionsBasic;
{$APPTYPE CONSOLE}
// A function returns a value via the implicit Result variable.
function Add(A, B: Integer): Integer;
begin
Result := A + B;
end;
// A procedure performs an action but returns nothing.
procedure Greet(const Name: string);
begin
WriteLn('Hello, ', Name, '!');
end;
begin
WriteLn('3 + 4 = ', Add(3, 4));
Greet('Delphi');
end.
A few things to notice:
function Add(A, B: Integer): Integer;— parametersAandBshare the typeInteger, and the part after the colon declares the return type.Result— Delphi’s implicit return variable. You assign to it instead of using areturnstatement. (The older Pascal style of assigning to the function’s own name,Add := A + B, also works.)procedure Greet— no return type, because procedures don’t return values.
Parameters: Modes and Default Values
Delphi gives you precise control over how parameters are passed:
- By value (the default): the routine gets a copy; changes don’t affect the caller.
const: read-only; the compiler forbids modification and can pass large values efficiently.var: passed by reference; changes are visible to the caller.out: likevar, but signals the parameter is purely for output; its incoming value is ignored.
You can also give trailing parameters default values.
Create a file named parameters.dpr:
program Parameters;
{$APPTYPE CONSOLE}
// const parameter is read-only; Greeting has a default value.
procedure Greet(const Name: string; const Greeting: string = 'Hello');
begin
WriteLn(Greeting, ', ', Name, '!');
end;
// var parameter is passed by reference - the change is visible to the caller.
procedure Double(var Value: Integer);
begin
Value := Value * 2;
end;
// out parameters are used to return multiple values.
procedure SplitName(const FullName: string; out First, Last: string);
var
SpacePos: Integer;
begin
SpacePos := Pos(' ', FullName);
First := Copy(FullName, 1, SpacePos - 1);
Last := Copy(FullName, SpacePos + 1, Length(FullName));
end;
var
N: Integer;
FirstName, LastName: string;
begin
Greet('World'); // uses the default greeting
Greet('Delphi', 'Welcome'); // overrides the default
N := 21;
Double(N); // var parameter modifies N in place
WriteLn('Doubled: ', N);
SplitName('Anders Hejlsberg', FirstName, LastName);
WriteLn('First: ', FirstName);
WriteLn('Last: ', LastName);
end.
The Double procedure changes its caller’s variable because Value is a var parameter. The SplitName procedure uses two out parameters to hand back two strings at once — a common Delphi idiom when a function can only return one value but you need several results.
Recursion
A function that calls itself is recursive. Delphi handles recursion naturally, and the classic examples are factorial and Fibonacci.
Create a file named recursion.dpr:
program Recursion;
{$APPTYPE CONSOLE}
uses
SysUtils; // for IntToStr and Trim
// Factorial: N! = N * (N-1) * ... * 1
function Factorial(N: Integer): Int64;
begin
if N <= 1 then
Result := 1
else
Result := N * Factorial(N - 1);
end;
// Fibonacci: each number is the sum of the previous two.
function Fib(N: Integer): Integer;
begin
if N < 2 then
Result := N
else
Result := Fib(N - 1) + Fib(N - 2);
end;
var
I: Integer;
Sequence: string;
begin
for I := 1 to 6 do
WriteLn(I, '! = ', Factorial(I));
Sequence := '';
for I := 0 to 9 do
Sequence := Sequence + IntToStr(Fib(I)) + ' ';
WriteLn('Fibonacci: ', Trim(Sequence));
end.
Factorial returns an Int64 because factorials grow quickly and would overflow a 32-bit Integer. Each call to Factorial(N) reduces the problem to Factorial(N - 1) until it reaches the base case N <= 1.
Scope and Nested Routines
A local variable, declared inside a routine, exists only while that routine runs. A global variable, declared in the main program’s var section, is visible everywhere. Delphi (like all Pascal dialects) also supports nested routines — a procedure defined inside another procedure, which can see the enclosing routine’s local variables.
Create a file named scope.dpr:
program Scope;
{$APPTYPE CONSOLE}
var
// Global variable: visible to every routine in the program.
CallCount: Integer;
procedure DoWork;
var
// Local variable: exists only during this call.
LocalValue: Integer;
begin
Inc(CallCount); // modifies the global
LocalValue := CallCount * 10;
WriteLn('Call #', CallCount, ' -> local value ', LocalValue);
end;
// Outer contains a nested routine, Inner.
procedure Outer;
var
Shared: Integer;
// Inner is nested inside Outer and can see Outer's local variables.
procedure Inner;
begin
Shared := Shared + 1;
end;
begin
Shared := 100;
Inner;
Inner;
WriteLn('Shared after two Inner calls: ', Shared);
end;
begin
CallCount := 0;
DoWork;
DoWork;
DoWork;
Outer;
end.
DoWork reads and modifies the global CallCount, so its value persists between calls. LocalValue, by contrast, starts fresh each call. Inside Outer, the nested Inner procedure shares access to Outer’s Shared variable — something not possible in languages without nested functions.
Higher-Order Routines with Procedural Types
Delphi lets you declare a procedural type — a type whose values are functions. You can store a function in a variable or pass one as a parameter, which is the foundation of higher-order routines.
Create a file named higher_order.dpr:
program HigherOrder;
{$APPTYPE CONSOLE}
uses
SysUtils; // for IntToStr and Trim
type
// A procedural type: any function taking an Integer and returning an Integer.
TIntFunc = function(X: Integer): Integer;
function Square(X: Integer): Integer;
begin
Result := X * X;
end;
function Cube(X: Integer): Integer;
begin
Result := X * X * X;
end;
// MapRange takes a function as a parameter and applies it across a range.
function MapRange(Func: TIntFunc; Lo, Hi: Integer): string;
var
I: Integer;
begin
Result := '';
for I := Lo to Hi do
Result := Result + IntToStr(Func(I)) + ' ';
Result := Trim(Result);
end;
begin
WriteLn('Squares: ', MapRange(Square, 1, 5));
WriteLn('Cubes: ', MapRange(Cube, 1, 5));
end.
TIntFunc describes the shape of a function: it takes one Integer and returns an Integer. Both Square and Cube match that shape, so they can be passed to MapRange, which applies whichever function it receives to every value in the range.
Running with Docker
These examples run with Free Pascal in Delphi compatibility mode (-Mdelphi). The compiler names each executable after its source file (without the extension).
| |
Expected Output
Running functions_basic:
3 + 4 = 7
Hello, Delphi!
Running parameters:
Hello, World!
Welcome, Delphi!
Doubled: 42
First: Anders
Last: Hejlsberg
Running recursion:
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
Fibonacci: 0 1 1 2 3 5 8 13 21 34
Running scope:
Call #1 -> local value 10
Call #2 -> local value 20
Call #3 -> local value 30
Shared after two Inner calls: 102
Running higher_order:
Squares: 1 4 9 16 25
Cubes: 1 8 27 64 125
Key Concepts
- Functions vs procedures — a
functionreturns a value; aproceduredoes not. This distinction is explicit in Delphi and communicates intent clearly. - The
Resultvariable — inside a function, assign the return value toResult. Assigning to the function name also works butResultis the modern, preferred style. - Parameter modes matter — by value (a copy),
const(read-only and efficient),var(by reference, changes visible to caller), andout(for returning data). Choosing the right mode documents intent and improves performance. - Default parameter values let you make trailing parameters optional, reducing the need for overloaded routines.
- Scope is lexical — local variables live only during a call, globals persist, and Delphi’s nested routines can access their enclosing routine’s locals.
- Recursion is natural — define a base case to stop the recursion and a recursive case that moves toward it. Watch your return types: factorials need
Int64, notInteger. - Procedural types enable higher-order routines — declare a type that describes a function’s signature, then pass matching functions as values to build reusable, generic logic.
Running Today
All examples can be run using Docker:
docker pull freepascal/fpc:3.2.2-slim
Comments
Loading comments...
Leave a Comment