Algorithms & Pseudocode
This is the Paper 2 half of the course: designing algorithms, choosing data structures, writing pseudocode that actually runs the way you think it does, and testing it properly. Paper 2 is entirely pseudocode, there's no multiple choice to hide behind, so this page is built around worked programs you can trace line by line, not just definitions to memorise. Covers Cambridge 9618 syllabus sections 9.1 to 12.3.
Paper 2 tests four connected syllabus sections as one continuous skill: section 9, breaking a problem down and designing an algorithm for it; section 10, choosing the right data type or structure to hold the data (arrays, records, files, and the abstract data types: stacks, queues, linked lists); section 11, writing that algorithm as pseudocode using selection, iteration, procedures and functions; and section 12, the development process around it, structure charts, and how you test and maintain the finished program. This page treats them as one page for exactly that reason, most Paper 2 questions ask you to move between all four in a single answer.
Computational Thinking & Designing Algorithms
Before you write a single line of pseudocode, Paper 2 expects you to be able to talk about how you got there. That is what section 9 of the syllabus is really testing: not "can you code", but "can you take a messy real-world problem and turn it into a precise, ordered set of steps".
Abstraction
Abstraction is the process of removing detail that is not relevant to the problem you are solving, so you are left with a simplified model that only contains what matters. A school attendance system does not need to model the colour of a student's bag or what they had for breakfast, it only needs a student ID, a name, and whether they were present. Abstraction exists because real-world problems are full of irrelevant detail, and trying to model all of it makes a solution slower to design, harder to code, and harder to get right. A good abstraction keeps the essential data and behaviour and discards the rest.
Decomposition
Decomposition is breaking a large problem down into smaller sub-problems that are easier to solve individually. Each sub-problem typically becomes a module in the final program, in pseudocode terms a procedure or a function (see section 10). A "library loan system", for example, decomposes naturally into smaller jobs: check whether a student is allowed to borrow, register a new loan, process a return, and count a student's current loans. Section 11 of this page builds exactly that system, one sub-problem at a time.
"Describe how abstraction/decomposition was used" questions are common on Paper 2 and are answered in words, not pseudocode. Name the technique, then say specifically what was removed (abstraction) or which sub-problem was separated out and why (decomposition). A vague "it makes it simpler" answer rarely scores full marks.
What is an algorithm?
An algorithm is a solution to a problem expressed as a sequence of precisely defined steps that, if followed exactly, always produce the correct result. Every algorithm can be built from just three basic constructs, combined and nested in any order:
Steps carried out one after another, in the order they are written.
A choice between two or more paths, based on a condition (IF, CASE). See section 3.
A block of steps repeated, either a fixed number of times or until/while a condition holds (FOR, WHILE, REPEAT UNTIL). See section 4.
Every algorithm on this page, however complex, is built by nesting only these three constructs inside one another.
Identifier names and identifier tables
Every variable in an algorithm needs a name (an identifier) that is meaningful and describes what it stores, StudentCount rather than x. Before writing pseudocode for a non-trivial problem, it is good practice, and sometimes explicitly asked for in an exam question, to document the identifiers you plan to use in an identifier table: the name, its data type, and what it represents.
For a program that reads temperature readings until a negative value is entered, then reports the highest, lowest, average and count (built fully in section 4):
| Identifier | Data type | Description |
|---|---|---|
temp | REAL | The current temperature reading entered by the user |
highest | REAL | The highest valid reading seen so far |
lowest | REAL | The lowest valid reading seen so far |
count | INTEGER | The number of valid readings taken |
sum | REAL | The running total of all valid readings, used to compute the average |
Documenting an algorithm: structured English, flowcharts, pseudocode
The same algorithm can be documented in three different ways, and Paper 2 can ask you to convert between any of them:
- Structured English: a numbered, indented list of steps written in plain but precise language, close to pseudocode but without strict syntax.
- Flowchart: a diagram using standard symbols (rounded start/end, rectangle for a process, diamond for a decision, parallelogram for input/output), connected by arrows showing the flow of control.
- Pseudocode: the formal, syntax-precise language this whole page is written in, close enough to real code that it can be translated directly into any programming language.
Question (assumed): A login system has a fixed password. The user gets up to three attempts to enter it correctly, and the system reports whether access was granted or denied. Design the algorithm before coding it.
1. Decomposition: this is small enough to stay as one module, but it clearly separates into "collect an attempt", "check it", and "report the outcome".
2. Structured English:
3. Flowchart (in words): start → initialise attempts and access → [loop start] input password → decision: correct? → yes: set access granted; no: decrement attempts → decision: access granted OR attempts = 0? → no: back to loop start; yes: exit loop → decision: access granted? → output "Access Granted" or "Access Denied" → end.
4. Stepwise refinement is repeating this process, at each pass adding one more level of detail, until the description is precise enough to become pseudocode directly, for instance turning "check it" into the exact Boolean condition password = correctPass. The full REPEAT UNTIL pseudocode for this exact system is built in section 4, once the loop constructs it needs have been introduced.
Parts of an algorithm, especially conditions in selection and iteration, are usually defined using logic statements: Boolean expressions built from comparisons (=, <>, <, >, <=, >=) combined with AND, OR and NOT. attempts > 0 AND access = FALSE is a logic statement; get comfortable reading these, section 3 and 4 use them constantly.
Pseudocode Basics: Data Types, Variables & I/O
Cambridge pseudocode has a fixed, small set of rules. Learn them once here and every worked example on this page will already make sense.
The six basic data types
| Type | Holds | Example values |
|---|---|---|
INTEGER | Whole numbers, positive or negative | 67, -12, 10534 |
REAL | Numbers with a decimal point | 3.142, 9.673, 8.00 |
CHAR | A single character | '3', 'Q', 'c' |
STRING | Any sequence of characters | "ABCD", "A03455", "hello123" |
BOOLEAN | Exactly one of two values | TRUE, FALSE |
DATE | A calendar date | 01/09/2026 |
CHAR values are written in single quotes, STRING values in double quotes, this distinction is easy to lose marks on.
Rules for naming variables
- A variable name must start with a letter.
- It cannot contain spaces,
CountryNamenotCountry Name. - It cannot contain special characters (only letters and digits after the first letter).
- Variable names are case-sensitive:
Nameandnameare two different identifiers, this is a common source of "undeclared variable" style errors in student pseudocode.
Declaring, initialising and assigning
A variable should be declared with its type before use, and a fixed value that never changes is declared as a constant:
CONSTANT MaxAttempts <- 3 DECLARE Name : STRING DECLARE Age : INTEGER Name <- "Ali" Age <- 17
The assignment operator is <-, it means "the value on the right is stored in the variable on the left". It is not the same as the comparison operator =, which is only used inside a condition. Writing IF Flag <- TRUE THEN instead of IF Flag = TRUE THEN is one of the most common syntax slips on Paper 2, see the "spot the error" exercises in section 14.
Operators
| Kind | Operators |
|---|---|
| Arithmetic | + - * / DIV (integer division) MOD (remainder) ^ (power) |
| Comparison | = <> < > <= >= |
| Logical | AND OR NOT |
| String concatenation | & |
Input and output
OUTPUT "Enter a number" INPUT num OUTPUT num * 2
OUTPUT displays a value or message, INPUT reads a value from the keyboard into a variable that must already be declared.
The syllabus lists a small set of built-in routines you are expected to know (rounding, converting between types), but the exam always gives you any string manipulation function you need, along with what it does. The most common ones that appear across this page:
| Function | Does |
|---|---|
LENGTH(str) | Returns the number of characters in a string |
MID(str, start, count) | Returns count characters from str, beginning at position start |
LEFT(str, n) / RIGHT(str, n) | Returns the first / last n characters of str |
UCASE(str) / LCASE(str) | Converts a string to upper / lower case |
NUM_TO_STR(num) / STR_TO_NUM(str) | Converts a number to a string, and back |
INT(x) / RAND(n) | Truncates a real to an integer / returns a random real from 0 up to (not including) n |
Selection: IF and CASE
Selection is a choice between different paths of execution, based on whether a condition is TRUE or FALSE.
IF, ELSE and nested IF
IF Scores[index] > 100 THEN
count <- count + 1
OUTPUT CricketTeams[index]
ENDIF
An IF on its own only runs its block when the condition is TRUE, execution otherwise skips straight past ENDIF. Add an ELSE to give a second path for when the condition is FALSE:
IF count > 0 THEN
OUTPUT "The number of teams that scored over 100 is ", count
ELSE
OUTPUT "No team scored over 100"
ENDIF
IF statements can be nested, an IF placed inside the THEN or ELSE branch of another IF, to test a second condition only once the first has already been satisfied. This is exactly how a "running highest and lowest" tracker works:
IF temp > 0 THEN
IF temp > highest THEN
highest <- temp
ENDIF
IF temp < lowest THEN
lowest <- temp
ENDIF
count <- count + 1
sum <- sum + temp
ENDIF
Note the outer IF here is a validation guard, a common exam pattern where the outer condition filters out data (only positive, real readings) before any of the inner logic runs at all.
CASE
When one variable is being compared against several possible values, a chain of nested IFs becomes hard to read. A CASE statement expresses this more clearly:
Question (assumed): Write pseudocode that takes an integer mark out of 100 and outputs a letter grade: A for 70 and above, B for 60 to 69, C for 50 to 59, D for 40 to 49, and U (ungraded) for anything below 40.
OUTPUT "Enter mark"
INPUT mark
CASE OF mark DIV 10
9, 8, 7 : OUTPUT "Grade A"
6 : OUTPUT "Grade B"
5 : OUTPUT "Grade C"
4 : OUTPUT "Grade D"
OTHERWISE : OUTPUT "Grade U"
ENDCASE
mark DIV 10 collapses any two-digit mark down to its tens digit, so 9, 8 and 7 catch every mark from 70 to 99 in one line. OTHERWISE is the CASE equivalent of a final ELSE, it catches every value not explicitly listed above it.
Use CASE when one single variable is being tested against a fixed list of discrete values or ranges, it is shorter and clearer. Use nested IF when the conditions involve different variables, or comparisons that CASE cannot express cleanly (compound conditions with AND/OR, or comparing two different variables to each other). An examiner asking you to "justify" a choice between them wants exactly this reasoning, not just "IF is more flexible".
Iteration: FOR, WHILE, REPEAT UNTIL
Iteration repeats a block of steps. Cambridge pseudocode gives you three loop constructs, and choosing the right one for a given problem is a skill the exam tests directly.
Use when you know in advance exactly how many times the loop must run.
Checks the condition before each pass, so the body can run zero times if the condition is false to begin with.
Checks the condition after each pass, so the body always runs at least once.
Known iteration count → FOR. Unknown count, might need zero runs → WHILE. Unknown count, must run at least once (e.g. "ask at least once") → REPEAT UNTIL.
Compound conditions and input validation
A REPEAT UNTIL or WHILE condition is often a compound Boolean expression using AND/OR, most commonly to validate a range of user input. Both loops below do the same job, keep asking until the number is between 1 and 100 inclusive, they simply check the opposite condition at the opposite point in the loop:
REPEAT
OUTPUT "Enter a number between 1 and 100"
INPUT num
UNTIL num >= 1 AND num <= 100
OUTPUT "Enter a number between 1 and 100"
INPUT num
WHILE num < 1 OR num > 100
OUTPUT "Enter a number between 1 and 100"
INPUT num
ENDWHILE
Notice the condition flips: REPEAT UNTIL exits when the input is valid (num>=1 AND num<=100), WHILE keeps looping while it is invalid (num<1 OR num>100), the two are logical opposites of each other (De Morgan's law). The WHILE version also needs one input statement written before the loop starts, since the condition has to be checked before the first pass has any data to check.
Worked example: sentinel-controlled loop
Question: A weather station takes temperature readings (°C) throughout the day. Write pseudocode that continuously inputs readings and stops when a negative value is entered (meaning the sensor has stopped). It should track the highest reading, the lowest reading, the number of valid readings taken, and their average, and output all four values at the end.
highest <- -9999
lowest <- 9999
count <- 0
sum <- 0
REPEAT
OUTPUT "Enter temperature in Celsius"
INPUT temp
IF temp > 0 THEN
IF temp > highest THEN
highest <- temp
ENDIF
IF temp < lowest THEN
lowest <- temp
ENDIF
count <- count + 1
sum <- sum + temp
ENDIF
UNTIL temp < 0
OUTPUT "The average temperature is ", sum / count
OUTPUT "The highest temperature is ", highest
OUTPUT "The lowest temperature is ", lowest
OUTPUT "The number of readings taken is ", count
This is called a sentinel-controlled loop, a special value (here, any negative number) signals "stop", rather than a fixed count or a range check. REPEAT UNTIL is the natural choice, the station must take at least one reading before it can ever check for the stop signal.
Arrays: 1D and 2D
An array is a data structure that stores a fixed number of values of the same data type under one identifier, each accessed by a numeric position.
Terminology
- Index: the position used to access a specific element,
StdMarks[7]accesses the element at index 7. - Lower bound: the smallest valid index (in Cambridge pseudocode, arrays are usually declared starting at index 1).
- Upper bound: the largest valid index.
DECLARE StdMarks : ARRAY[1:50] OF INTEGER
Here the lower bound is 1 and the upper bound is 50, so StdMarks holds exactly 50 integers, accessed as StdMarks[1] through StdMarks[50].
1D arrays: initialise, input, output
DECLARE StdMarks : ARRAY[1:50] OF INTEGER
// Initialise every element to 0
FOR index <- 1 TO 50
StdMarks[index] <- 0
NEXT index
// Take input from the user and store it in the array
FOR index <- 1 TO 50
OUTPUT "Enter student mark"
INPUT StdMarks[index]
NEXT index
// Output the array
FOR index <- 1 TO 50
OUTPUT StdMarks[index]
NEXT index
Initialising, filling, and reading an array is almost always done with a count-controlled FOR loop, the array's fixed size makes the number of iterations known in advance, exactly the case FOR is built for.
Question: Create an array named StudentNames that stores the names of 100 students. Take input from the user to fill it, then output every name.
DECLARE StudentNames : ARRAY[1:100] OF STRING
FOR index <- 1 TO 100
OUTPUT "Enter name of student"
INPUT StudentNames[index]
NEXT index
FOR index <- 1 TO 100
OUTPUT StudentNames[index]
NEXT index
2D arrays
A 2D array stores data indexed by two dimensions, typically thought of as rows and columns, useful anywhere data is naturally a grid: a seating plan, a set of test scores across several students and several tests, or a game board.
DECLARE TestScores : ARRAY[1:5, 1:3] OF INTEGER
This declares a grid with 5 rows and 3 columns, 15 elements in total, one INTEGER per student per test. TestScores[2,3] accesses row 2, column 3: student 2's mark on test 3. Filling or reading a 2D array needs nested FOR loops, one for each dimension:
Question (assumed): Store the marks for 5 students across 3 tests in a 2D array, taking input row by row, then output each student's total mark across all three tests.
DECLARE TestScores : ARRAY[1:5, 1:3] OF INTEGER
DECLARE student, test, total : INTEGER
// Input: outer loop = student (row), inner loop = test (column)
FOR student <- 1 TO 5
FOR test <- 1 TO 3
OUTPUT "Enter mark for student ", student, " test ", test
INPUT TestScores[student, test]
NEXT test
NEXT student
// Output each student's total across all three tests
FOR student <- 1 TO 5
total <- 0
FOR test <- 1 TO 3
total <- total + TestScores[student, test]
NEXT test
OUTPUT "Student ", student, " total: ", total
NEXT student
TestScores, a 4 row by 4 column array. Click any cell to see exactly how it is written in pseudocode.Array Algorithms: Linear Search & Bubble Sort
The syllabus explicitly requires two array algorithms: searching with a linear search, and sorting with a bubble sort. Both are built here around one running example.
A cricket league has 12 teams. Store each team's name and score in two parallel arrays. Take input for all 12 teams, calculate the total and average score, find how many teams scored over 100 and output their names, then let the user search for a team by name and sort the teams into score order.
Setting up: parallel arrays
DECLARE Scores : ARRAY[1:12] OF INTEGER
DECLARE CricketTeams : ARRAY[1:12] OF STRING
DECLARE sum, count, index : INTEGER
sum <- 0
count <- 0
FOR index <- 1 TO 12
OUTPUT "Enter name of team ", index
INPUT CricketTeams[index]
OUTPUT "Enter score for ", CricketTeams[index]
INPUT Scores[index]
sum <- sum + Scores[index]
IF Scores[index] > 100 THEN
count <- count + 1
OUTPUT CricketTeams[index]
ENDIF
NEXT index
OUTPUT "Total score: ", sum
OUTPUT "Average score: ", sum / 12
Scores and CricketTeams are parallel arrays, index 5 in one always describes the same team as index 5 in the other. This only works if every operation that moves data in one array (like sorting, below) moves the corresponding element in the other array too.
Linear search
A linear search checks each element in turn, from the first to the last, until it finds a match or runs out of elements. It works on an array in any order, but in the worst case has to check every single element.
OUTPUT "Enter the name of the team to find"
INPUT nameToFind
index <- 1
Found <- FALSE
REPEAT
IF CricketTeams[index] = nameToFind THEN
Found <- TRUE
ELSE
index <- index + 1
ENDIF
UNTIL Found = TRUE OR index > 12
IF Found = TRUE THEN
OUTPUT "Found at position ", index
ELSE
OUTPUT "Team not found"
ENDIF
index must only be set to 1 once, before the loop starts. Resetting it to 1 inside the loop body, a real mistake worth knowing to spot, either checks position 1 forever (infinite loop, since index never grows past 1) or wastes every pass re-checking the start of the array. Section 14 works through this exact bug as a "find the error" exercise.
Bubble sort
A bubble sort repeatedly steps through the array, comparing each pair of neighbouring elements and swapping them if they are in the wrong order. Each full pass "bubbles" the largest remaining unsorted value up to its correct position, so the algorithm needs at most n-1 passes for n elements. A pass with no swaps means the array is already sorted, so the loop can stop early.
DECLARE pass, temp, temp2 : INTEGER
DECLARE swap : BOOLEAN
pass <- 1
REPEAT
swap <- FALSE
FOR index <- 1 TO 12 - pass
IF Scores[index] > Scores[index + 1] THEN
swap <- TRUE
temp <- Scores[index]
Scores[index] <- Scores[index + 1]
Scores[index + 1] <- temp
temp2 <- CricketTeams[index]
CricketTeams[index] <- CricketTeams[index + 1]
CricketTeams[index + 1] <- temp2
ENDIF
NEXT index
pass <- pass + 1
UNTIL swap = FALSE OR pass > 12
median <- (Scores[6] + Scores[7]) / 2
OUTPUT "Median score: ", median
The inner FOR only needs to go up to 12 - pass: after each pass, one more element at the end of the array is guaranteed sorted and can be skipped. Swapping Scores[index] and Scores[index+1] is always followed by the matching swap in CricketTeams, this is what keeps the two parallel arrays lined up.
Records and User-Defined Types
An array is powerful, but it can only hold one data type. Real data rarely looks like that, a single student has a name (STRING), a date of birth (DATE) and a grade average (REAL) all at once. A record solves this: it groups related data of different types together under one identifier, so they can be handled as a single unit.
Defining a record type
In Cambridge pseudocode a record structure is defined once with TYPE ... ENDTYPE, then used as a data type in its own right:
TYPE StudentRecord
DECLARE StudentID : STRING
DECLARE Name : STRING
DECLARE DateOfBirth : DATE
DECLARE GradeAverage : REAL
ENDTYPE
DECLARE Student1 : StudentRecord
Student1 is now a single variable that internally holds all four fields. An individual field is accessed with a dot: Student1.Name, Student1.GradeAverage.
Reading from and writing to a record
OUTPUT "Enter student ID" INPUT Student1.StudentID OUTPUT "Enter student name" INPUT Student1.Name Student1.GradeAverage <- 78.5 OUTPUT "Student ", Student1.Name, " has average ", Student1.GradeAverage
Arrays of records
Records and arrays combine constantly: an array where every element is a full record is how you model a whole table of real-world data in pseudocode, one student, one book, one loan per element, each with several fields. This is exactly the structure the library system in section 11 is built on:
TYPE LoanRecord
DECLARE StudentID : STRING
DECLARE BookID : STRING
DECLARE OnLoan : BOOLEAN
ENDTYPE
DECLARE Loan : ARRAY[1:5000] OF LoanRecord
// Access the StudentID field of loan record 12
OUTPUT Loan[12].StudentID
Both group multiple values together, the difference is the data type. A 2D array holds many values of the same type in a grid, a record holds a fixed set of values of different types under one identifier. An array of records combines both ideas: many records, each with several differently-typed fields.
Abstract Data Types: Stacks, Queues, Linked Lists
An abstract data type (ADT) is a collection of data together with the set of operations defined on that data, described by its behaviour rather than by how it is actually stored in memory. Stacks, queues and linked lists are the three ADTs named explicitly by the syllabus.
The syllabus is explicit: you are not required to write full pseudocode implementations of a stack, queue or linked list. You do need to be able to add, edit and delete data from each one, describe their key features, justify which one suits a given situation, and describe how each can be implemented using an array.
Last In, First Out. Data is only added (pushed) and removed (popped) from one end, the "top". The most recently added item is always the first one removed.
Used for: undo history, a browser's back button, evaluating expressions, tracking function calls (call stack).
First In, First Out. Data is added (enqueued) at the "rear" and removed (dequeued) from the "front". The first item added is always the first one removed.
Used for: a print queue, tasks waiting for a processor, messages waiting to be sent in order.
A sequence of nodes, each holding a data value and a pointer to the next node. Unlike an array, elements do not need to sit in consecutive memory, so inserting or deleting an item in the middle only means updating two pointers, not shifting every following element along.
Used for: any ordered collection that is frequently inserted into or deleted from in the middle, for instance keeping a list of names in alphabetical order as new names arrive.
Implementing each one using an array
| ADT | Array-based implementation |
|---|---|
| Stack | A fixed-size array, plus one integer variable Top holding the index of the current top element. Push increases Top then stores the new value there; pop reads the value at Top then decreases it. |
| Queue | A fixed-size array, plus two integer variables Front and Rear. Enqueue stores at Rear then increases it; dequeue reads from Front then increases it. Often implemented as a circular array so the indices wrap back to the start instead of running off the end. |
| Linked list | Two parallel arrays, one holding the data values and one holding, for each element, the array index of the "next" element, plus a separate variable holding the index of the first (head) element. Following the chain of "next" indices visits the list in order without the data itself ever having to move. |
File Handling
Every variable and array covered so far only exists while the program is running, once it ends, that data is gone. Files exist to solve exactly that: they let a program save data permanently to storage, and read it back in on a later run.
Opening, reading, writing and closing a text file
| Statement | Does |
|---|---|
OPENFILE FileName FOR READ | Opens an existing file so its lines can be read |
OPENFILE FileName FOR WRITE | Creates a new file (or empties an existing one) so lines can be written to it |
OPENFILE FileName FOR APPEND | Opens an existing file so new lines can be added to the end, without deleting what is already there |
READFILE FileName, Variable | Reads the next line from the file into Variable |
WRITEFILE FileName, Value | Writes Value as a new line in the file |
EOF(FileName) | Returns TRUE once every line has been read, "end of file" |
CLOSEFILE FileName | Closes the file, always call this once you are done, an unclosed file can lose data or lock the file for other programs |
Because a text file can hold any number of lines, and a program does not know that number in advance, reading a file almost always uses a WHILE loop controlled by EOF, a pre-condition loop is the right choice here since a completely empty file should read zero lines, not one:
OPENFILE "Data.txt" FOR READ
WHILE EOF("Data.txt") = FALSE
READFILE "Data.txt", OneLine
OUTPUT OneLine
ENDWHILE
CLOSEFILE "Data.txt"
Question (assumed): A student's project file contains lines of pseudocode, some of which end with a comment starting // (everything from the // to the end of the line is the comment). Write a function DeleteComment that takes one line as a STRING and returns it with any comment removed, then write a function Stage_1 that copies every line of a named student's source file into a new "stage 1" file with comments stripped, skipping any line that becomes blank, and returns the number of lines written.
FUNCTION DeleteComment(Line : STRING) RETURNS STRING
DECLARE index, LineLength : INTEGER
DECLARE ThisChar : CHAR
DECLARE InComment : BOOLEAN
DECLARE ReturnString : STRING
ReturnString <- ""
InComment <- FALSE
LineLength <- LENGTH(Line)
FOR index <- 1 TO LineLength
ThisChar <- MID(Line, index, 1)
IF InComment = FALSE THEN
IF ThisChar = '/' AND MID(Line, index, 2) = "//" THEN
InComment <- TRUE
ELSE
ReturnString <- ReturnString & ThisChar
ENDIF
ENDIF
NEXT index
RETURN ReturnString
ENDFUNCTION
FUNCTION Stage_1(StdName : STRING) RETURNS INTEGER
DECLARE StdFileName, OutFileName, OneLine, CleanLine : STRING
DECLARE LineCount : INTEGER
StdFileName <- StdName & "_src.txt"
OutFileName <- StdName & "_S1.txt"
LineCount <- 0
OPENFILE StdFileName FOR READ
OPENFILE OutFileName FOR WRITE
WHILE EOF(StdFileName) = FALSE
READFILE StdFileName, OneLine
CleanLine <- DeleteComment(OneLine)
IF LENGTH(CleanLine) <> 0 THEN
WRITEFILE OutFileName, CleanLine
LineCount <- LineCount + 1
ENDIF
ENDWHILE
CLOSEFILE StdFileName
CLOSEFILE OutFileName
RETURN LineCount
ENDFUNCTION
Two things worth noticing: Stage_1 is a function that calls another function, DeleteComment, exactly the kind of decomposition section 1 introduced, and the comment-checking logic reads one character at a time using MID, comparing a two-character lookahead against "//" rather than trying to track a single slash across two separate passes.
Procedures and Functions
Structured programming means building a solution from named, reusable blocks instead of one long unbroken sequence of statements. Cambridge pseudocode gives you two kinds of block.
A named block of steps that performs an action. Called as a statement on its own line. Does not return a value.
Use when: the block's job is to do something, output a message, update a record, run a routine, and nothing needs to be handed back to plug into an expression.
A named block that calculates and returns a single value. Called from inside an expression, its return value replaces the call.
Use when: the block's job is to work something out, a Boolean check, a total, a converted string, that the calling code then needs to use.
Terminology
| Term | Meaning |
|---|---|
| Header | The first line: PROCEDURE Name(...) or FUNCTION Name(...) RETURNS Type |
| Interface | The header, considered as everything a caller needs to know to use the block: its name, its parameters, and (for a function) its return type, without needing to see how it works inside |
| Parameter | The placeholder name declared in the header, e.g. a in PROCEDURE Swap(BYREF a : INTEGER, BYREF b : INTEGER) |
| Argument | The actual value supplied when the block is called, e.g. the 5 and x in Swap(5, x) |
| Return value | The single value a function sends back to the point it was called from, via RETURN |
A procedure or function can be declared with no parameters, one, or several, separated by commas in the header.
BYVAL and BYREF
Each parameter is passed in one of two ways, and the syllabus expects you to know the difference:
- BYVAL (by value): a copy of the argument is passed in. Any change made to the parameter inside the block has no effect on the caller's original variable.
- BYREF (by reference): the parameter becomes a direct link to the caller's original variable. Any change made to the parameter inside the block changes that original variable too.
PROCEDURE Swap(BYREF a : INTEGER, BYREF b : INTEGER)
DECLARE temp : INTEGER
temp <- a
a <- b
b <- temp
ENDPROCEDURE
x <- 5
y <- 9
CALL Swap(x, y)
OUTPUT x, y // outputs 9, 5: the caller's variables actually changed
If a and b were declared BYVAL instead, Swap would still run correctly inside itself, but x and y back in the calling code would be completely unaffected, the swap would be thrown away the moment the procedure ended.
x and y after the same Swap procedure runs, once with BYVAL parameters, once with BYREF.Worked example: a pure function
Question: Write a function IsRA that takes the coordinates of a triangle's three vertices (six integers) and returns TRUE if the triangle has a right angle, using the fact that for a right-angled triangle, the square of the longest side equals the sum of the squares of the other two (the converse of Pythagoras' theorem).
FUNCTION IsRA(x1, y1, x2, y2, x3, y3 : INTEGER) RETURNS BOOLEAN
DECLARE Side1, Side2, Side3 : INTEGER
DECLARE RA : BOOLEAN
Side1 <- (x1 - x2)^2 + (y1 - y2)^2
Side2 <- (x1 - x3)^2 + (y1 - y3)^2
Side3 <- (x2 - x3)^2 + (y2 - y3)^2
RA <- FALSE
IF (Side1 = Side2 + Side3) OR (Side2 = Side1 + Side3) OR (Side3 = Side1 + Side2) THEN
RA <- TRUE
ENDIF
RETURN RA
ENDFUNCTION
// Called from inside an IF condition, its return value is used directly:
IF IsRA(0, 0, 4, 0, 0, 3) THEN
OUTPUT "Right-angled"
ENDIF
IsRA takes six parameters, all BYVAL by default since none of them need to change for the caller, and is called directly inside an IF condition, this is the signature of a function: its return value is used in place of the call itself.
The syllabus specifically asks for "efficient" pseudocode. In practice that means: do not repeat a calculation that has already been stored in a variable, stop a loop as soon as the answer is known (the bubble sort in section 6 exits early once a pass makes no swaps), and use a function instead of copy-pasting the same block of logic in three different places.
Worked Programs: Putting It Together
Paper 2's longest questions rarely test one idea in isolation, they hand you a scenario and expect records, arrays, selection, iteration and functions to all show up in the same answer. These two worked programs are built exactly that way.
Case study: a library loan system
A college library tracks up to 5000 active and past loans using the LoanRecord array declared in section 7 (StudentID, BookID, OnLoan). An empty slot has StudentID = "". Write:
- a procedure
OKToBorrow(sID)that outputs whether a student may borrow another book, the college limits each student to 5 books on loan at once - a function
NewLoan(sID, bID)that records a new loan in the first free slot and returns whether it succeeded - a function
ReturnBook(sID, bID)that marks a matching loan as returned and returns whether it succeeded - a procedure
CountLoans(sID)that outputs how many books a student currently has on loan, and how many they have returned in total
PROCEDURE OKToBorrow(sID : STRING)
DECLARE index, count : INTEGER
DECLARE LimitReached : BOOLEAN
count <- 0
LimitReached <- FALSE
FOR index <- 1 TO 5000
IF (Loan[index].StudentID = sID) AND (Loan[index].OnLoan = TRUE) THEN
count <- count + 1
ENDIF
NEXT index
IF count >= 5 THEN
LimitReached <- TRUE
ENDIF
IF LimitReached = TRUE THEN
OUTPUT "Cannot borrow, already 5 books on loan"
ELSE
OUTPUT "OK to borrow"
ENDIF
ENDPROCEDURE
FUNCTION NewLoan(sID : STRING, bID : STRING) RETURNS BOOLEAN
DECLARE index : INTEGER
DECLARE Found : BOOLEAN
index <- 1
Found <- FALSE
WHILE index <= 5000 AND Found = FALSE
IF Loan[index].StudentID = "" THEN
Loan[index].StudentID <- sID
Loan[index].BookID <- bID
Loan[index].OnLoan <- TRUE
Found <- TRUE
ELSE
index <- index + 1
ENDIF
ENDWHILE
RETURN Found
ENDFUNCTION
FUNCTION ReturnBook(sID : STRING, bID : STRING) RETURNS BOOLEAN
DECLARE index : INTEGER
DECLARE Found, AlreadyReturned : BOOLEAN
index <- 1
Found <- FALSE
AlreadyReturned <- FALSE
WHILE index <= 5000 AND Found = FALSE
IF (Loan[index].StudentID = sID) AND (Loan[index].BookID = bID) THEN
Found <- TRUE
IF Loan[index].OnLoan = TRUE THEN
Loan[index].OnLoan <- FALSE
ELSE
AlreadyReturned <- TRUE
ENDIF
ENDIF
index <- index + 1
ENDWHILE
IF Found = TRUE AND AlreadyReturned = FALSE THEN
RETURN TRUE
ELSE
RETURN FALSE
ENDIF
ENDFUNCTION
PROCEDURE CountLoans(sID : STRING)
DECLARE index, Borrowed, Returned : INTEGER
Borrowed <- 0
Returned <- 0
FOR index <- 1 TO 5000
IF Loan[index].StudentID = sID THEN
IF Loan[index].OnLoan = TRUE THEN
Borrowed <- Borrowed + 1
ELSE
Returned <- Returned + 1
ENDIF
ENDIF
NEXT index
OUTPUT "Currently on loan: ", Borrowed
OUTPUT "Returned: ", Returned
ENDPROCEDURE
Every routine here scans the same Loan array, but reaches for a different loop: OKToBorrow and CountLoans always check all 5000 slots, so a count-controlled FOR fits; NewLoan and ReturnBook can stop the moment they find what they are looking for, so a condition-controlled WHILE with a Found flag is both correct and more efficient.
Three mistakes are extremely common in exactly this kind of question, and are worth training your eye to catch, in your own work and in "identify the error" questions (section 14): using = where <- is needed for an assignment (or the reverse, inside a condition); using two different array bounds for the same array in different routines; and using a Boolean flag variable (like Found) in a loop condition without declaring and initialising it immediately beforehand.
Case study: pattern printing
Write a procedure Square(SquareLength), where SquareLength is an integer from 1 to 9, that outputs a square pattern SquareLength characters wide and tall: the border made of the digit SquareLength itself, and the interior filled with *. For example, Square(4) should output:
PROCEDURE Square(SquareLength : INTEGER)
DECLARE SquareVal, TempStr : STRING
DECLARE row, col : INTEGER
SquareVal <- NUM_TO_STR(SquareLength)
IF SquareLength = 1 THEN
OUTPUT SquareVal
ELSE
IF SquareLength = 2 THEN
OUTPUT SquareVal & SquareVal
OUTPUT SquareVal & SquareVal
ELSE
// top border row
TempStr <- ""
FOR col <- 1 TO SquareLength
TempStr <- TempStr & SquareVal
NEXT col
OUTPUT TempStr
// middle rows: border character, then stars, then border character
FOR row <- 1 TO SquareLength - 2
TempStr <- ""
FOR col <- 1 TO SquareLength - 2
TempStr <- TempStr & "*"
NEXT col
TempStr <- SquareVal & TempStr & SquareVal
OUTPUT TempStr
NEXT row
// bottom border row
TempStr <- ""
FOR col <- 1 TO SquareLength
TempStr <- TempStr & SquareVal
NEXT col
OUTPUT TempStr
ENDIF
ENDIF
ENDPROCEDURE
This needs three separate cases (1, 2, and everything else) because a square smaller than 3 characters wide has no room for a distinct border and interior, always check your loop bounds against the smallest possible input, not just a "typical" one, this is exactly the instinct section 14's test data section trains.
Program Development Life Cycle
A development life cycle is a structured set of stages a team follows to turn a problem into a working, maintained piece of software. Its purpose is to make development predictable and manageable, without one, larger projects tend to drift, miss requirements, or ship with untested assumptions. Every life cycle model is built from the same five underlying stages:
Understand and document exactly what the system must do, gathering requirements from the people who will use it.
Plan the solution before coding it: data structures, algorithms, structure charts, interfaces.
Translate the design into working pseudocode/program code.
Check the program behaves correctly, see section 14.
5. Maintenance: keep the finished, deployed program working and up to date over time, also covered in section 14.
Different projects need different life cycles, different ways of arranging and repeating those five stages, depending on how well understood the requirements are, how large the system is, and how much risk the team can tolerate.
| Model | How it works | Benefit | Drawback |
|---|---|---|---|
| Waterfall | Each stage is completed and signed off in strict order before the next begins, analysis, then design, then coding, then testing, then maintenance. | Clear structure, thorough documentation at every stage, easy to manage and track progress against a plan. | Very expensive to go back a stage once the project has moved on, poor fit when requirements are unclear or likely to change. |
| Iterative | The five stages are repeated in a series of cycles, each cycle delivers a more complete version of the system, informed by feedback on the previous one. | Problems and misunderstood requirements surface early, working software exists from an early cycle onward, easier to adapt to change. | Harder to fix an overall budget or deadline in advance, needs continuous user involvement, can lose sight of the overall architecture if not managed carefully. |
| RAD (Rapid Application Development) | Prioritises building working prototypes quickly with heavy, continuous user involvement, rather than extensive up-front documentation. | Very fast delivery, users see and react to something concrete almost immediately, well suited to smaller, well-understood systems on a tight deadline. | Reduced up-front analysis can miss requirements, less suited to very large or safety-critical systems, prototypes can end up shipped as production code before they are robust enough. |
Program Design: Structure Charts & State-Transition Diagrams
Both of these are design tools used before coding starts, to plan a program's shape on paper first.
Structure charts
A structure chart shows how a problem has been decomposed into modules (procedures and functions), arranged as a hierarchy, and exactly what data passes between them. Each box is a module, each line connecting two boxes represents a call from the box above to the box below, and each line is labelled with a data couple, an arrow alongside it showing what data flows and in which direction: pointing down into the module for a parameter, pointing up out of it for a returned value.
| Module | Data passed in | Data passed back |
|---|---|---|
| OKToBorrow | sID (STRING) | none, a procedure |
| NewLoan | sID, bID (STRING) | Success (BOOLEAN) |
| ReturnBook | sID, bID (STRING) | Success (BOOLEAN) |
| CountLoans | sID (STRING) | none, a procedure |
Deriving pseudocode from a structure chart is essentially reading it top to bottom: the top box becomes the main program (or a top-level procedure) that calls each of the four modules below it, passing exactly the parameters the data couples specify, and using any returned value the way the chart shows it flowing back up.
State-transition diagrams
A state-transition diagram documents a system that moves between a fixed set of distinct states, based on events. Each state is drawn as a circle (or rounded box), and each possible move between two states is a labelled arrow showing the event that triggers it, and any action taken.
Three states, Locked, Granted, Denied, and the system only ever moves along one of the three labelled arrows shown. This is a natural fit for any system whose behaviour genuinely depends on "what has happened so far", a login attempt limiter, a vending machine, a traffic light sequence, rather than for a straight-line calculation with no memory of past events.
Use a structure chart to document how a problem was decomposed and how modules pass data to each other. Use a state-transition diagram to document how a single entity's behaviour changes over time in response to events. A question asking you to "document the design of" a modular program wants a structure chart; one asking you to "document the behaviour of" a system with modes or stages wants a state-transition diagram.
Testing and Maintenance
A program is not finished when it compiles or runs without crashing, it is finished when it has been shown to do the right thing, including on the inputs nobody expected. This section is where "add extra content on test data types" lives, and it is genuinely one of the highest-yield topics on Paper 2.
Types of error
| Error type | What it means | Example |
|---|---|---|
| Syntax error | The code breaks the rules of the language itself, it will not even run. | Opening a block with FUNCTION ... RETURNS BOOLEAN but closing it with ENDPROCEDURE instead of ENDFUNCTION. |
| Logic error | The code runs without crashing, but produces the wrong result because the algorithm itself is flawed. | Resetting a linear search's index back to 1 inside the loop body (section 6), the search either never advances or repeatedly rechecks the same element. |
| Run-time error | The code is syntactically valid and logically reasonable, but fails while actually executing, because of the specific data it is given. | Computing sum / count in the Temperature Monitor (section 4) when count is still 0, division by zero. |
Testing methods
| Method | What it involves |
|---|---|
| Dry run | Manually tracing the pseudocode by hand, line by line, using a trace table (section 4) to predict what it should output. |
| Walkthrough | A structured, planned dry run performed in front of colleagues, so a team can review logic together before it is coded. |
| White-box testing | Tests designed with knowledge of the code's internal structure, chosen so every possible path/branch through the code is executed at least once. |
| Black-box testing | Tests designed purely from what the program is supposed to do (its specification), with no knowledge of how it is coded internally. |
| Stub | A temporary, simplified placeholder for a module that has not been written yet, so the rest of the program can still be tested. |
| Integration testing | Testing that separately-written modules work correctly once combined together. |
| Alpha testing | Testing carried out in-house, by the development team or organisation, before any outside release. |
| Beta testing | Testing carried out by a limited group of real, external users, using a near-final version, before general release. |
| Acceptance testing | Formal testing, usually by or on behalf of the client, to confirm the finished system actually meets the original requirements. |
Test strategy and test plan
A test strategy is the overall approach: which of the methods above will be used, in what order, and why. A test plan turns that strategy into a concrete, executable checklist, so testing is systematic rather than ad hoc. A test plan typically records, for every test: a test number, what it is checking, the exact test data used, the type of that data (see below), the expected outcome, the actual outcome once run, and a pass/fail result.
Choosing test data: normal, boundary, abnormal
Good test data is chosen deliberately to expose faults, not just to confirm the program works when everything goes right. Three categories are expected by name:
Sensible, everyday data that a valid run of the program should clearly accept and process correctly.
Data sitting exactly on, or one either side of, the edge of what is valid, this is where off-by-one errors hide.
Data the program should reject: out of range, the wrong type, or otherwise invalid, chosen to check the program fails safely rather than crashing or accepting bad data.
Recall the "enter a number between 1 and 100" loop from section 4. A proper test plan for it needs data from all three categories:
| Test data | Category | Why |
|---|---|---|
| 50 | Normal | An ordinary, clearly valid value well inside the range. |
| 1 and 100 | Boundary | The exact edges of the valid range, must both be accepted. |
| 0 and 101 | Boundary | One step just outside each edge, must both be rejected, this is what actually catches an off-by-one mistake like > written instead of >=. |
| -5000 and "abc" | Abnormal | Wildly invalid values (out of range, wrong type), the program must reject these without crashing. |
Maintenance
Maintenance is everything that happens to a program after it is deployed and in use. The syllabus names three types:
Fixing a fault that was found after release, an error that slipped through testing (for instance, discovering the linear search bug from section 6 in production and patching it).
Changing the program so it keeps working correctly as its environment changes, a new operating system, a changed file format, a new legal requirement.
Improving the program beyond fixing faults, better performance, a clearer interface, new features the users have asked for.
Spot the error
FUNCTION ReturnBook(sID: STRING, bID: STRING) RETURNS BOOLEAN but closes the block with ENDPROCEDURE. What kind of error is this, and how is it fixed?This is a syntax error: the block was opened as a FUNCTION but closed as a PROCEDURE, the two keywords do not match. It is fixed by changing ENDPROCEDURE to ENDFUNCTION, matching the opening keyword. See the corrected version in section 11.
index <- 1 inside the loop body instead of once before it starts. What kind of error is this, and what does it actually do when run?This is a logic error, the pseudocode is syntactically valid and will run, but it never produces a correct search. Since index is reset to 1 at the start of every pass, the loop repeatedly checks only the first element and index can never grow past 1, so the loop condition index > 12 is never reached: it becomes an infinite loop unless the very first element happens to match. The fix is to initialise index <- 1 once, before the loop begins, as shown in section 6.
