Subroutines

AS Level

Procedures & Functions (AS Level)

Master the CIE AS Level (9618) subroutines topic: PROCEDURE / FUNCTION declarations, the mandatory RETURN statement, parameters and arguments, BYVALUE vs BYREF passing, local vs global scope, built-in functions, stepwise refinement and common pitfalls, the instant-exit behaviour of RETURN inside a loop, using built-in string functions inside your own functions, and combining subroutines with text file handling.

1.1 What are Subroutines?

A subroutine is a named, self-contained block of code that performs a specific task. Once defined, it can be called (invoked) from anywhere in the program — meaning you write the logic once and reuse it many times.

  • Reusability: write the code once, call it from many places.
  • Modularity: break a large program into smaller, manageable pieces.
  • Readability: well-named subroutines act as abstractions — the reader sees what is being done without needing to read the details.
  • Easier debugging & testing: each subroutine can be tested in isolation before being integrated.
  • Abstraction: callers do not need to know how the subroutine works, only what it does.

CIE 9618 pseudocode has two kinds of subroutine:

Procedure

Performs an action (e.g. prints output, updates a value passed BYREF) but does not return a value.

CALL Greet("Sam")

Function

Performs a calculation and returns exactly one value via the RETURN statement.

Total <- Add(3, 4)

Key idea: the choice between a procedure and a function is determined by whether you need a value back. If the subroutine's job is to do something (print, update a flag, modify an array), use a procedure. If its job is to compute something (sum, max, average), use a function.

1.2 Procedures: Declaration & Calling

A procedure is declared between PROCEDURE and ENDPROCEDURE. It is invoked with the CALL keyword.

Syntax:

PROCEDURE Name(Parameter1 : Type, Parameter2 : Type, ...)
  // body — statements that perform the action
ENDPROCEDURE

// Calling the procedure:
CALL Name(Arg1, Arg2, ...)

Example — a procedure that greets a user by name:

PROCEDURE Greet(Name : STRING)
  OUTPUT "Hello, ", Name
  OUTPUT "Welcome to Pseudocode Hub."
ENDPROCEDURE

// Main program
DECLARE User : STRING
INPUT User
CALL Greet(User)

When CALL Greet(User) runs, the value of User is passed into the parameter Name. The two OUTPUT statements run, then control returns to the line after the CALL.

Example — a parameterless procedure:

PROCEDURE PrintDivider()
  OUTPUT "------------------------"
ENDPROCEDURE

CALL PrintDivider()
OUTPUT "Report Title"
CALL PrintDivider()
  • Parameters are optional: empty parentheses () are allowed when the procedure needs no input.
  • No RETURN statement: a procedure never uses RETURN to send back a value. (It can use the bare RETURN keyword to exit early, but this is rare at AS Level.)
  • Always call with CALL: writing Greet("Sam") without CALL is a syntax error in 9618 pseudocode.

Warning: a procedure call is a statement, not an expression. You cannot write X <- MyProc(5) because MyProc returns nothing. Use CALL MyProc(5) as a standalone line, or change the procedure into a function.

1.3 Functions: Declaration & Calling

A function is declared between FUNCTION and ENDFUNCTION. It must declare the type of value it returns (using RETURNS) and must contain at least one RETURN statement.

Syntax:

FUNCTION Name(Parameter1 : Type, ...) RETURNS ReturnType
  // body — compute the result
  RETURN Value   // mandatory — sends Value back to the caller
ENDFUNCTION

// Calling the function — use it in an expression:
DECLARE Result : ReturnType
Result <- Name(Arg1, ...)

Example — a function that adds two integers:

FUNCTION Add(A : INTEGER, B : INTEGER) RETURNS INTEGER
  DECLARE Sum : INTEGER
  Sum <- A + B
  RETURN Sum
ENDFUNCTION

// Main program
DECLARE Result : INTEGER
Result <- Add(3, 4)
OUTPUT Result   // Outputs: 7

Functions can also be used directly inside expressions — there is no need to assign them to a variable first:

OUTPUT Add(10, 20)              // Outputs: 30
DECLARE Total : INTEGER
Total <- Add(2, 3) + Add(4, 5)   // Total = 5 + 9 = 14
IF Add(X, Y) > 100 THEN
  OUTPUT "Big"
ENDIF
  • Mandatory RETURN: every function must execute a RETURN that sends back a value of the declared type. Forgetting RETURN is a compile-time error.
  • Exactly one value: a function returns exactly one value. To send back multiple pieces of information, use several BYREF parameters on a procedure, or return an array.
  • No CALL keyword: functions are called by writing their name and arguments in an expression. The result is then used like any other value.
  • Multiple RETURN points are allowed but a single RETURN at the end is generally easier to read and debug.

Pattern: the RETURN statement does two things at once — it sends a value back to the caller and it immediately terminates the function. Any code after RETURN inside the same block is unreachable. A single RETURN at the end of the function is the cleanest pattern.

1.4 Parameters & Arguments

  • Formal parameters are the named variables listed in the subroutine header — e.g. PROCEDURE Greet(Name : STRING) Name is the parameter.
  • Actual arguments are the concrete values supplied at the call site — e.g. CALL Greet("Sam") "Sam" is the argument.
  • The number, order and type of arguments must match the parameters exactly.

CIE 9618 pseudocode supports two passing mechanisms: BYVALUE (the default for scalars) and BYREF (by reference).

BYVALUE (default)

PROCEDURE Double(X : INTEGER)
  X <- X * 2
ENDPROCEDURE

DECLARE Num : INTEGER
Num <- 5
CALL Double(Num)
OUTPUT Num   // 5 — caller's Num unchanged

BYREF (by reference)

PROCEDURE Double(BYREF X : INTEGER)
  X <- X * 2
ENDPROCEDURE

DECLARE Num : INTEGER
Num <- 5
CALL Double(Num)
OUTPUT Num   // 10 — caller's Num changed!

How BYVALUE works: the value of the argument is copied into the parameter. The subroutine works on its own private copy. Changes to the parameter inside the subroutine do not affect the caller's variable. This is the safe default.

How BYREF works: instead of copying a value, the subroutine receives a reference (the memory address) to the caller's actual variable. Assigning to the parameter inside the subroutine modifies the caller's variable directly. This is useful when a subroutine needs to send back more than one result, or when working with large data structures.

Side-by-side trace — calling Double(Num) where Num = 5

Step
BYVALUE
BYREF
Num (main)
5
5
X (param)
5 (copy)
alias of Num
After X <- X * 2
X = 10, Num = 5
Num = 10 (same cell!)
OUTPUT Num
5
10

Arrays are passed BYREF by default in 9618 pseudocode — even without writing the BYREF keyword. This is for efficiency (no need to copy every element) and means any changes the subroutine makes to array elements will be visible to the caller after the call returns.

PROCEDURE FillZeros(BYREF Arr : ARRAY[1:5] OF INTEGER)
  DECLARE I : INTEGER
  FOR I <- 1 TO 5
    Arr[I] <- 0
  NEXT I
ENDPROCEDURE

DECLARE Data : ARRAY[1:5] OF INTEGER
CALL FillZeros(Data)
// Data is now [0, 0, 0, 0, 0] — the array was modified directly.

Rule of thumb: use BYVALUE when the subroutine only needs to read the input. Use BYREF when the subroutine needs to modify the caller's variable (or when you need to send back more than one result, since functions return only one value).

1.5 Local vs Global Variables

Every variable in a 9618 program has a scope — the region of the program where the variable is visible and can be used.

  • Local variables: declared inside a subroutine using DECLARE. Visible only inside that subroutine. Created when the subroutine is called, destroyed when it ends (lifetime = duration of the call).
  • Global variables: declared at the top of the program (outside any subroutine) using GLOBAL. Visible from anywhere in the program. Live for the entire run of the program.
GLOBAL Counter : INTEGER       // Global — visible everywhere
Counter <- 0

PROCEDURE Increment()
  DECLARE Step : INTEGER       // Local — visible only inside Increment
  Step <- 1
  Counter <- Counter + Step    // Can read AND write the global
ENDPROCEDURE

CALL Increment()
CALL Increment()
OUTPUT Counter   // 2

// OUTPUT Step   // ERROR — Step is local to Increment, not visible here

Why local variables are preferred:

  • Avoid side-effects: a local can only be changed by its own subroutine, so you cannot accidentally break another part of the program.
  • Prevent name collisions: two subroutines can both have a local called Count or Temp without any conflict.
  • Save memory: locals only exist while their subroutine is running; the memory is freed when the subroutine ends.
  • Easier to test & reuse: a subroutine that only uses its parameters and locals can be lifted into another program without dragging globals along.

Shadowing: if a local variable is declared with the same name as a global, the local "shadows" the global inside that subroutine — references use the local, and the global is untouched. Shadowing is legal but confusing; avoid it by using distinct names.

GLOBAL X : INTEGER
X <- 100

PROCEDURE Confuse()
  DECLARE X : INTEGER   // Local X shadows the global X
  X <- 5                // Changes the LOCAL X, not the global
  OUTPUT X              // 5
ENDPROCEDURE

CALL Confuse()
OUTPUT X                // 100 — the global was never changed

Warning: globals make debugging hard. If a global is changed by a call to ProcA, and ProcA calls ProcB which also uses the global, the order of calls suddenly matters in surprising ways. Prefer parameters and return values over shared globals.

1.6 Built-ins, Stepwise Refinement & Pitfalls

Built-in functions are pre-defined by the language — you do not declare them, just call them. Common 9618 built-ins include:

// String built-ins
LENGTH("Hello")       // 5  — number of characters
UPPER("Hello")        // "HELLO"
LOWER("HELLO")        // "hello"
SUBSTRING("Hello", 2, 3)  // "ell"  (start position, length)

// Numeric built-ins
ROUND(3.14159, 2)     // 3.14  — round to 2 decimal places
RANDOM(6)             // random integer in range 1..6
13 MOD 5              // 3     — remainder (operator, not function)
13 DIV 5              // 2     — integer quotient (operator, not function)

Contrast these with user-defined functions — the ones you write yourself using FUNCTION. Both are called the same way (used in an expression), but built-ins are always available without any declaration.

Stepwise refinement (also called top-down design) is the standard method for tackling large problems. Start with the whole problem, split it into a few major sub-tasks, then keep splitting each sub-task until every piece is small enough to implement as a single subroutine. Subroutines are the natural unit of decomposition.

Worked example — design a program that reads 10 student marks and prints the average and the highest mark. Top-level decomposition:

// Top level: main program reads input, then reports results
DECLARE Marks : ARRAY[1:10] OF INTEGER
ReadMarks(Marks)                       // Procedure — fills the array
DECLARE Avg : REAL
Avg <- CalculateAverage(Marks)         // Function  — returns the mean
DECLARE Best : INTEGER
Best <- FindHighest(Marks)             // Function  — returns the max
OUTPUT "Average = ", Avg
OUTPUT "Highest = ", Best

Each subroutine can now be written and tested independently. The main program reads almost like English — that is the power of well-named subroutines combined with stepwise refinement.

Common pitfalls to avoid:

  • Forgetting RETURN in a function — the function declares RETURNS INTEGER but never executes a RETURN statement. Compile-time error.
  • Mismatched parameter count or types — calling Add(3) when the function expects two parameters, or passing a STRING where an INTEGER is required.
  • Shadowing globals with locals — a local with the same name as a global hides the global inside the subroutine, leading to surprising behaviour. Use distinct names.
  • Accidentally mutating caller data via BYREF — passing BYREF when BYVALUE would do means the subroutine silently modifies the caller's variable. Default to BYVALUE; use BYREF only when you intend to mutate.
  • Using a procedure like a function — writing X <- MyProc(5) where MyProc is a procedure. Procedures return nothing, so this is illegal. Use CALL MyProc(5) or change MyProc into a function.
  • Forgetting that arrays are passed BYREF by default — modifications to array elements inside the subroutine are visible to the caller afterwards. If you need to keep the original array intact, pass a copy.

Key points summary — a quick recap of everything you need to remember about Procedures & Functions at AS Level:

A subroutine is a named, reusable block of code that performs a specific task.
A PROCEDURE performs an action but returns no value; a FUNCTION returns exactly one value.
Procedures are called with CALL Name(args); functions are used in expressions: x <- f(args).
A FUNCTION must contain a RETURN statement that sends back a value of the declared type.
Parameters are the variables in the subroutine header; arguments are the values passed at the call site.
BYVALUE (default for scalars) copies the value — the caller's variable is not changed.
BYREF passes a reference — changes inside the subroutine affect the caller's variable.
Arrays are passed BYREF by default in 9618 pseudocode.
Variables declared inside a subroutine are LOCAL; GLOBAL declarations live at the top of the program.
Prefer locals over globals: avoids side-effects, name collisions, and saves memory.

Remember: a procedure performs an action (called with CALL); a function computes and returns one value (used in an expression, must contain RETURN). BYVALUE copies (default for scalars), BYREF aliases (default for arrays). Locals are safer than globals. Subroutines are the building blocks of stepwise refinement.

1.7 RETURN Inside a Loop — Instant Exit

The RETURN statement does two things at once: it sends a value back to the caller and it immediately terminates the function. Crucially, this happens even if RETURN is executed inside a loop — the loop does not continue, and any code after the RETURN (inside or outside the loop) is never executed.

Important rule: any code written after a RETURN statement — whether inside or outside a loop — is not executed. RETURN is an instant exit.

Example — find the first even number in an array and return it immediately:

FUNCTION FindFirstEven(NumberList : ARRAY[1:5] OF INTEGER) RETURNS INTEGER
  DECLARE i : INTEGER
  FOR i <- 1 TO 5
    IF NumberList[i] MOD 2 = 0 THEN
      RETURN NumberList[i]   // exits the function HERE
    ENDIF
  NEXT i
  RETURN -1   // only runs if NO even number was found
ENDFUNCTION

DECLARE Numbers : ARRAY[1:5] OF INTEGER
Numbers[1] <- 3
Numbers[2] <- 7
Numbers[3] <- 8
Numbers[4] <- 5
Numbers[5] <- 2
DECLARE Result : INTEGER
Result <- FindFirstEven(Numbers)
OUTPUT Result   // Outputs: 8

Step-by-step trace:

  • i = 1: 3 MOD 2 = 1 → not even, continue.
  • i = 2: 7 MOD 2 = 1 → not even, continue.
  • i = 3: 8 MOD 2 = 0 → even! RETURN NumberList[3] runs.
  • The function instantly stops. Value 8 is returned.
  • The loop does not check i = 4 or i = 5.
  • The final RETURN -1 is also ignored — it only runs if the loop finishes without finding an even number.

When is this useful? When you only need the first matching item and want to stop early to save time. The rest of the loop is unnecessary once the condition is met. The pattern RETURN value inside the loop plus RETURN sentinel after the loop is the standard "find first or report not-found" structure.

1.8 String Functions Inside User-Defined Functions

In Paper 2 you are often asked to write a FUNCTION that processes a string — extract part of a string, validate a code, format output, or convert case. To do this you combine a user-defined function with the built-in string functions provided by 9618 pseudocode.

Common 9618 built-in string functions:

FunctionReturnsExample
LEFT(s, n)First n charactersLEFT("Computer", 3)"Com"
RIGHT(s, n)Last n charactersRIGHT("Computer", 3)"ter"
MID(s, start, len)len chars from position startMID("Computer", 4, 3)"put"
LENGTH(s)Number of charactersLENGTH("Computer")8
TO_UPPER(s)Uppercase versionTO_UPPER("abc")"ABC"
STR_TO_NUM(s)String converted to numberSTR_TO_NUM("20")20
NUM_TO_STR(n)Number converted to stringNUM_TO_STR(25)"25"

Example 1 — extract the first 3 characters using LEFT:

FUNCTION GetPrefix(Text : STRING) RETURNS STRING
  DECLARE Prefix : STRING
  Prefix <- LEFT(Text, 3)
  RETURN Prefix
ENDFUNCTION

OUTPUT GetPrefix("Computer")   // Outputs: Com

Example 2 — extract the month from a date "DD/MM/YYYY" using MID:

FUNCTION GetMonth(DateValue : STRING) RETURNS STRING
  DECLARE Month : STRING
  Month <- MID(DateValue, 4, 2)   // position 4, length 2
  RETURN Month
ENDFUNCTION

OUTPUT GetMonth("25/12/2025")   // Outputs: 12

Example 3 — validate a password using LENGTH (returns BOOLEAN):

FUNCTION CheckPassword(Password : STRING) RETURNS BOOLEAN
  IF LENGTH(Password) >= 8 THEN
    RETURN TRUE
  ELSE
    RETURN FALSE
  ENDIF
ENDFUNCTION

OUTPUT CheckPassword("abc")           // Outputs: FALSE
OUTPUT CheckPassword("mypassword")    // Outputs: TRUE

Example 4 — combining LENGTH, MID and LEFT to get initials from "Ali Khan":

FUNCTION GetInitials(FullName : STRING) RETURNS STRING
  DECLARE FirstInitial : STRING
  DECLARE SpacePosition : INTEGER
  DECLARE SecondInitial : STRING
  DECLARE i : INTEGER

  SpacePosition <- 0
  // Find the position of the space character
  FOR i <- 1 TO LENGTH(FullName)
    IF MID(FullName, i, 1) = " " THEN
      SpacePosition <- i
    ENDIF
  NEXT i

  FirstInitial <- LEFT(FullName, 1)
  SecondInitial <- MID(FullName, SpacePosition + 1, 1)

  RETURN FirstInitial & SecondInitial
ENDFUNCTION

OUTPUT GetInitials("Ali Khan")   // Outputs: AK

Example 5 — convert a string number, add 5, return a string (STR_TO_NUM + NUM_TO_STR):

FUNCTION IncreaseNumber(Value : STRING) RETURNS STRING
  DECLARE NumberValue : INTEGER
  DECLARE Result : STRING

  NumberValue <- STR_TO_NUM(Value)   // "20" -> 20
  NumberValue <- NumberValue + 5      // 20 + 5 = 25
  Result <- NUM_TO_STR(NumberValue)   // 25 -> "25"

  RETURN Result
ENDFUNCTION

OUTPUT IncreaseNumber("20")   // Outputs: 25

Pattern: convert STRING to INTEGER before arithmetic, and INTEGER to STRING before returning a string. You cannot do maths on a STRING, so STR_TO_NUM is required first; if the function signature says RETURNS STRING, the final value must be a string, so NUM_TO_STR is required last.

1.9 Text Files with Procedures & Functions

A common AS Level Paper 2 task is to wrap file operations inside a procedure (when no value needs to come back) or a function (when you must return a count, a result, or a TRUE/FALSE). The file commands are OPENFILE, WRITEFILE, READFILE, CLOSEFILE and EOF().

The three file modes:

  • FOR WRITE — creates a new file (or overwrites an existing one; old data is deleted).
  • FOR APPEND — adds new data to the END of an existing file; original content is preserved.
  • FOR READ — opens the file for input; use with WHILE NOT EOF(FileName).

Example 1 — a PROCEDURE that creates a file and writes one line (FOR WRITE):

PROCEDURE CreateFileAndWrite(FileName : STRING, Data : STRING)
  OPENFILE FileName FOR WRITE
  WRITEFILE FileName, Data
  CLOSEFILE FileName
ENDPROCEDURE

CALL CreateFileAndWrite("Test.txt", "Hello World")

Example 2 — a PROCEDURE that appends a line to an existing file (FOR APPEND):

PROCEDURE AppendToFile(FileName : STRING, NewData : STRING)
  OPENFILE FileName FOR APPEND
  WRITEFILE FileName, NewData
  CLOSEFILE FileName
ENDPROCEDURE

CALL AppendToFile("Test.txt", "Second Line")

Example 3 — a PROCEDURE that reads and displays every line (FOR READ + EOF loop):

PROCEDURE DisplayFile(FileName : STRING)
  DECLARE LineData : STRING
  OPENFILE FileName FOR READ
  WHILE NOT EOF(FileName)
    READFILE FileName, LineData
    OUTPUT LineData
  ENDWHILE
  CLOSEFILE FileName
ENDPROCEDURE

CALL DisplayFile("Test.txt")

Example 4 — a FUNCTION that counts the records in a file (returns a value, so it must be a function):

FUNCTION CountLines(FileName : STRING) RETURNS INTEGER
  DECLARE LineData : STRING
  DECLARE Counter : INTEGER

  Counter <- 0
  OPENFILE FileName FOR READ
  WHILE NOT EOF(FileName)
    READFILE FileName, LineData
    Counter <- Counter + 1
  ENDWHILE
  CLOSEFILE FileName

  RETURN Counter
ENDFUNCTION

DECLARE Total : INTEGER
Total <- CountLines("Log.txt")
OUTPUT "Records: ", Total

Example 5 — early-exit search in a SORTED file (combines RETURN-in-a-loop with file reading):

// File Stock.txt is sorted in ascending order of ItemNum.
// Return TRUE if the item is NEW (not already in the file),
// FALSE if it already exists.
FUNCTION CheckNewItem(LineData : STRING) RETURNS BOOLEAN
  DECLARE FileName : STRING
  DECLARE NewItemNum : STRING
  DECLARE FileLine : STRING
  DECLARE FileItemNum : STRING

  FileName <- "Stock.txt"
  NewItemNum <- LEFT(LineData, 4)

  OPENFILE FileName FOR READ
  WHILE NOT EOF(FileName)
    READFILE FileName, FileLine
    FileItemNum <- LEFT(FileLine, 4)
    IF FileItemNum = NewItemNum THEN
      CLOSEFILE FileName
      RETURN FALSE            // found — stop early
    ENDIF
    IF FileItemNum > NewItemNum THEN
      CLOSEFILE FileName
      RETURN TRUE             // passed it — stop early
    ENDIF
  ENDWHILE
  CLOSEFILE FileName
  RETURN TRUE                 // not found anywhere
ENDFUNCTION

Why sorted files are efficient: because the records are in ascending order, as soon as the current record key is greater than the target you know the target cannot appear later. The function can CLOSEFILE and RETURN early without reading the rest of the file — fewer comparisons, faster search.

Question Bank

Answer all questions, then press Submit Quiz to see your score.

0/18 answered

Question 1Multiple Choice

Which of the following best defines a subroutine?

Question 2True / False

Functions in CIE 9618 pseudocode must return exactly one value.

Question 3Multiple Choice

FUNCTION Square(N : INTEGER) RETURNS INTEGER
  RETURN N * N
ENDFUNCTION

DECLARE Ans : INTEGER
Ans <- Square(5)
OUTPUT Ans

Question 4Multiple Choice

Which keyword is used to declare a procedure in 9618 pseudocode?

Question 5True / False

When a parameter is passed BYREF, changes made to it inside the subroutine also affect the caller's original variable.

Question 6Multiple Choice

Which built-in function returns the number of characters in a string?

Question 7Multiple Choice

PROCEDURE Increment(X : INTEGER)
  X <- X + 10
ENDPROCEDURE

DECLARE Num : INTEGER
Num <- 10
CALL Increment(Num)
OUTPUT Num

Question 8True / False

Arrays are passed by value by default in CIE 9618 pseudocode.

Question 9Multiple Choice

FUNCTION AddTen(A : INTEGER) RETURNS INTEGER
  DECLARE Result : INTEGER
  Result <- A + 10
ENDFUNCTION

DECLARE X : INTEGER
X <- AddTen(5)
OUTPUT X

Question 10Multiple Choice

PROCEDURE AddFive(BYREF X : INTEGER)
  X <- X + 5
ENDPROCEDURE

DECLARE Num : INTEGER
Num <- 10
CALL AddFive(Num)
OUTPUT Num

Question 11True / False

Stepwise refinement is the process of breaking a large problem into smaller sub-tasks, each implemented as its own subroutine.

Question 12Multiple Choice

Which keyword is used to declare a global variable in 9618 pseudocode?

Question 13Multiple Choice

A function contains a FOR loop. Inside the loop is a RETURN statement. What happens when that RETURN executes?

Question 14True / False

In a function, any statements written after a RETURN statement (in the same block) will still be executed.

Question 15Multiple Choice

A date is stored as the string "DD/MM/YYYY", for example "25/12/2025". What does MID("25/12/2025", 4, 2) return?

Question 16Multiple Choice

You have the string "20" and you need to perform arithmetic on it. Which built-in function converts "20" to the integer 20?

Question 17Multiple Choice

A program must add a new log line to the end of an existing file "Log.txt" without erasing the current contents. Which file mode should be used?

Question 18True / False

When reading a text file of unknown length, use a WHILE NOT EOF(FileName) loop with READFILE inside it.

Answer all 18 questions to enable submission.