Procedures & Functions Grade 11
User-defined procedures and functions let you break code into reusable, named blocks. This makes programs easier to read, maintain and debug.
What Is a Method?
A method is a named block of code that performs a specific task. Instead of writing the same instructions over and over inside your event handlers, you write them once, give that block a name, and then simply call that name whenever you need the task done. In Delphi we build methods in two forms: procedures and functions.
Think of a method like a recipe card in a cookbook. The recipe is written down once. Whenever you want that dish, you don't rewrite the steps from scratch — you just fetch the card and follow it. A method works exactly the same way: write the steps once, then "fetch" them by name as often as you like.
Why Do We Break Code Into Methods?
When you are starting out it can feel like extra work to split your program into little named blocks. But experienced programmers do it for very good reasons:
- DRY (Don't Repeat Yourself) – if the same instructions appear in three places and you copy-paste them, you now have to fix bugs in three places. Put them in one method and you fix it once.
- Readability – a well-named method like
CalcAveragetells the reader exactly what is happening, far better than ten lines of arithmetic. - Easier testing and debugging – you can test one small method on its own instead of hunting through a giant block of code.
- Reuse – once written, a method can be called from many different buttons and events.
Each method should do only one job. If a method is trying to do several things at once, that is usually a sign it should be split into smaller methods.
What Is a Procedure?
A procedure is a named block of code that does a task but does not send a value back to where it was called. It simply carries out its instructions — showing a message, clearing some boxes, drawing on a canvas — and then control returns to the program.
A procedure is like asking someone to "switch off the lights". They go and do the job. You don't expect them to hand you anything back — you just expect the task to be done.
Procedures
Without Parameters
// Declaration (above the event handlers)
procedure ShowHello;
begin
ShowMessage('Hello!');
end;
// Calling it
procedure TForm1.btnShowClick(Sender: TObject);
begin
ShowHello; // called alone
end;With Parameters
procedure DisplayGreeting(sMessage, sName: string);
begin
ShowMessage(sMessage + ' ' + sName);
end;
// Call with arguments
DisplayGreeting('Good morning', 'Ms Coetzee');Parameters vs Arguments
A parameter is a placeholder name listed in the method's declaration — it stands for a value the method will receive. An argument is the actual value you pass in when you call the method. In short: parameters live in the declaration; arguments are the real values you supply at the call.
Think of a parameter as a labelled jar called "sName" sitting empty on the shelf when the recipe is written. The argument is the actual "Ms Coetzee" you scoop into that jar when you finally cook. The jar's label is the parameter; what you put in it is the argument.
Example: in DisplayGreeting(sMessage, sName: string) the names sMessage and sName are parameters. When you call DisplayGreeting('Good morning', 'Ms Coetzee'), the strings 'Good morning' and 'Ms Coetzee' are the arguments.
Value Parameters
A value parameter means the method receives a copy of the argument. The method can change that copy freely inside itself, but the original variable back in the calling code stays untouched. This is the default and safest kind of parameter, because the caller never has to worry about its variables being changed unexpectedly.
A value parameter is like giving someone a photocopy of a document. They can scribble all over their copy, but your original is safe at home.
Reference (VAR) Parameters
A reference parameter — Delphi calls it a VAR parameter, marked with the keyword var in the declaration — doesn't hand the method a copy of anything. It points the method straight at the caller's own variable, so any change made inside the method sticks after the method finishes and control returns to the caller.
A reference parameter is like handing someone your original document instead of a photocopy. Whatever they write on it is now permanently on your original — there's no separate copy shielding it.
procedure DoubleIt(var iNum: Integer);
begin
iNum := iNum * 2; // changes the CALLER's variable directly
end;
// Call — no assignment needed, iScore itself is changed
DoubleIt(iScore);Several of Delphi's own built-in procedures rely on VAR parameters to hand back a result without being functions — Inc and Dec are everyday examples:
| Statement | Equivalent to |
|---|---|
Inc(iScore, iBonus); | iScore := iScore + iBonus; |
Dec(iScore, iPenalty); | iScore := iScore - iPenalty; |
Here iScore is the VAR parameter — it's the one actually overwritten with a new total — while iBonus (or iPenalty) is only ever a value parameter that Delphi reads once and then discards.
Reach for a plain value parameter whenever a method only needs to look at the data. Switch to a VAR parameter the moment a method has to alter the caller's variable itself — a procedure that swaps two values around, or one that tops up a running total that lives outside the method, are both classic cases.
Returning More Than One Value
A function is limited to handing back a single value through Result. When a task genuinely needs to report back two or more separate pieces of information at once, write a procedure with more than one VAR parameter instead — each VAR parameter becomes its own "answer slot" that the procedure fills in, and every one of them is available to the caller as soon as the procedure finishes.
procedure CheckAttendance(iDaysPresent, iTotalDays: Integer; var bMeetsRule: Boolean; var sNote: string);
begin
bMeetsRule := (iDaysPresent / iTotalDays) >= 0.8;
if bMeetsRule then
sNote := 'Meets the 80% attendance requirement.'
else
sNote := 'Below the 80% attendance requirement.';
end;
// Call — both bOk and sMsg are filled in by this one call
CheckAttendance(iPresent, iTotal, bOk, sMsg);
lblStatus.Caption := sMsg;iDaysPresent and iTotalDays stay ordinary value parameters, since the procedure only needs to read them. bMeetsRule and sNote are VAR parameters, so after the call bOk holds whatever was assigned to bMeetsRule, and sMsg holds whatever was assigned to sNote — two results from one procedure call.
What Is a Function?
A function is a named block of code that does a task and then returns exactly one value back to where it was called. Inside the function you store the answer in the special variable Result, and that answer is handed back so you can use it — store it, display it, or calculate further with it.
A function is like asking someone "How much is the bread?" They go, check, and come back with an answer you can use. A function always brings something back; a procedure does not have to.
Functions
Without Parameters
function GetAnswer: Integer;
begin
Result := 42;
end;
// Must be assigned
iVal := GetAnswer;Besides assigning to Result, Delphi also lets you assign the return value straight to the function's own name — so GetAnswer := 42; works exactly the same as Result := 42; above. You'll come across both styles in Delphi code; Result is the more modern and readable habit, so it's the one used throughout this page.
With Parameters
In one line: function AddNumbers(num1, num2: Integer): Integer;
function AddNumbers(num1, num2: Integer): Integer;
begin
Result := num1 + num2;
end;
// Call
iAnswer := AddNumbers(5, 3); // = 8
lblResult.Caption := IntToStr(AddNumbers(iA, iB));Function Example — Calculate Average
function CalcAverage(iTotal, iCount: Integer): Real;
begin
if iCount = 0 then
Result := 0
else
Result := iTotal / iCount;
end;
// Use it
rAvg := CalcAverage(iSum, iNumStudents);
lblAvg.Caption := FloatToStr(rAvg);Procedures vs Functions
| Procedure | Function | |
|---|---|---|
| Returns a value? | No (or modifies var parameters) | Yes — always returns exactly one value |
| Called how? | Alone as a statement | As part of another statement (assigned) |
| Keyword | procedure | function |
Local vs Global Variables
Where you declare a variable decides which parts of your program are allowed to see it. This is called the variable's scope.
- Local variable — declared inside a procedure or function's own
varblock (between the method header and itsbegin). It can only be used inside that method. As soon as the method ends, the variable and its value disappear. - Global variable — declared at unit level, in the main
varblock near the top of the unit (below the form'stype ... end;block, alongsideForm1: TForm1;). It can be used and changed by any procedure or function in the whole program, and it keeps its value for as long as the program runs.
A local variable is like a note you scribble on a scrap of paper during one phone call — useful while you're on the call, but you throw it away the moment you hang up, and nobody in the next room ever sees it. A global variable is like a whiteboard on the office wall — anyone in any room can walk up, read it, or write on it, and what's written stays there until someone deliberately changes it.
procedure TForm1.btnClick(Sender: TObject);
var
sName : string; // local — only exists inside this procedure
iNumber : Integer;
begin
...
end;type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1 : TForm1;
sName : string; // global — every procedure in this unit can see and change it
iNumber : Integer;It is tempting to make everything global so you never have to worry about passing values around — but this makes bugs much harder to find, because any method could be the one that changed the value. Rather keep variables local and use parameters to pass values into a method and Result (or a var parameter) to pass values back out. Save global variables for values that genuinely need to be shared everywhere, such as settings used across the whole form.
Declaring a Method as Part of the Form's Class
Every procedure and function example so far has been a stand-alone routine, sitting in the unit above the event handlers. Delphi offers a second way to write your own methods: you can make a procedure or function part of the Form's own class, exactly like an event handler already is. To do this, add its declaration inside the Form's class(TForm) ... end; block — normally in the private section — and then write its full code down in the implementation section using the same TForm1.MethodName pattern you already recognise from event handlers.
Once a method is declared in the private section, click anywhere inside its name and press Ctrl+Shift+C. Delphi will generate an empty procedure TForm1.MethodName; begin ... end; skeleton in the implementation section for you to complete — the same shortcut you may already use to generate event handlers.
type
TForm1 = class(TForm)
btnCalc: TButton;
procedure btnCalcClick(Sender: TObject);
private
{ Private declarations }
procedure ShowAverage(iTotal, iCount: Integer); // declaration
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
// definition — the class name TForm1 goes in front of the method name
procedure TForm1.ShowAverage(iTotal, iCount: Integer);
begin
lblAverage.Caption := FloatToStr(iTotal / iCount);
end;Both styles are valid Delphi and do exactly the same job. A stand-alone procedure is quick for a small helper needed in only one place; making a method part of the Form's class is the tidier choice once several event handlers need to share it, because every part of the Form's behaviour — its event handlers and its helper methods alike — is then declared together inside one class.
Passing an Array as a Parameter
An array can be passed into a procedure or function just like any other value — but Delphi won't let you type the array's size directly into a parameter list (writing something like arrScores: array[1..5] of Integer as a parameter is not allowed). Instead, declare the array as a named type above the Form's class first, and then use that type name wherever you need it — in the parameter list, and in any var block that needs a matching array.
type
TQuizScores = array[1..5] of Integer; // declared above the Form's class
TForm1 = class(TForm)
...
private
function CalcTotal(arrScores: TQuizScores): Integer;
public
...
end;
implementation
function TForm1.CalcTotal(arrScores: TQuizScores): Integer;
var
i, iSum: Integer;
begin
iSum := 0;
for i := 1 to 5 do
iSum := iSum + arrScores[i];
Result := iSum;
end;Any variable declared with TQuizScores — for example arrQuiz1: TQuizScores; inside an event handler's own var block — can now be passed as the argument, because the variable and the parameter are built from the exact same named type.
Worked Example — Validate & Grade a Mark
A real button click usually calls several methods together. Here two functions (each returning a value) and one procedure (which just does a job) work as a team: one function checks the input is valid, another turns a mark into a symbol, and the procedure prints a line.
// FUNCTION — returns True/False (used to make a decision)
function IsValidMark(iMark: Integer): Boolean;
begin
Result := (iMark >= 0) and (iMark <= 100);
end;
// FUNCTION — returns a string (used inside an expression)
function GetSymbol(iMark: Integer): string;
begin
if iMark >= 80 then Result := 'A'
else if iMark >= 70 then Result := 'B'
else if iMark >= 60 then Result := 'C'
else if iMark >= 50 then Result := 'D'
else if iMark >= 40 then Result := 'E'
else if iMark >= 30 then Result := 'F'
else Result := 'G';
end;
// PROCEDURE — does a job, returns nothing
procedure TForm1.AddLine(sText: string);
begin
memOut.Lines.Add(sText);
end;
// BUTTON — ties them together
procedure TForm1.btnProcessClick(Sender: TObject);
var
iMark: Integer;
begin
iMark := StrToInt(edtMark.Text);
if IsValidMark(iMark) then // function used AS the condition
AddLine(IntToStr(iMark) + ' = symbol ' + GetSymbol(iMark)) // function inside an expression, then procedure prints
else
ShowMessage('Enter a mark between 0 and 100.');
end;Notice that IsValidMark and GetSymbol hand a value back that you use (in the if and inside the message), while AddLine just does the job of printing. That is the whole rule: if you need an answer back, write a function; if you only need something done, write a procedure.
Why Use Procedures and Functions?
To bring it all together, here are the practical benefits you gain every time you reach for a method instead of writing one long block of code:
- Break large programs into manageable pieces
- Reuse code from multiple event handlers — no duplication
- Easier to find and fix bugs
- Easier to update — change in one place, works everywhere
- Each method should perform only one task