AS Level 9618 · Paper 2 · Chapters 9-12

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.

14 sections Syllabus 9.1-12.3 Paper 2 · 2 hours · 75 marks 8 interactive tools

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.

1

Computational Thinking & Designing Algorithms

Syllabus 9.1, 9.2

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.

Why examiners ask about this

"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:

Sequence

Steps carried out one after another, in the order they are written.

Selection

A choice between two or more paths, based on a condition (IF, CASE). See section 3.

Iteration

A block of steps repeated, either a fixed number of times or until/while a condition holds (FOR, WHILE, REPEAT UNTIL). See section 4.

Sequence + selection + iteration

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.

Example identifier table

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):

IdentifierData typeDescription
tempREALThe current temperature reading entered by the user
highestREALThe highest valid reading seen so far
lowestREALThe lowest valid reading seen so far
countINTEGERThe number of valid readings taken
sumREALThe 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.
Worked example: from problem to pseudocode

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:

1. Set the number of attempts remaining to 3 2. Set access to "not granted" 3. REPEAT 4. Ask the user for a password 5. IF it matches the stored password THEN 6. Set access to "granted" 7. ELSE 8. Reduce attempts remaining by 1 9. UNTIL access is granted OR attempts remaining is 0 10. IF access is granted THEN output "Access Granted" ELSE output "Access Denied"

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.

Logic statements

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.


2

Pseudocode Basics: Data Types, Variables & I/O

Syllabus 10.1, 11.1

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

TypeHoldsExample values
INTEGERWhole numbers, positive or negative67, -12, 10534
REALNumbers with a decimal point3.142, 9.673, 8.00
CHARA single character'3', 'Q', 'c'
STRINGAny sequence of characters"ABCD", "A03455", "hello123"
BOOLEANExactly one of two valuesTRUE, FALSE
DATEA calendar date01/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, CountryName not Country Name.
  • It cannot contain special characters (only letters and digits after the first letter).
  • Variable names are case-sensitive: Name and name are 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:

pseudocode
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

KindOperators
Arithmetic+ - * / DIV (integer division) MOD (remainder) ^ (power)
Comparison= <> < > <= >=
LogicalAND OR NOT
String concatenation&

Input and output

pseudocode
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.

Built-in functions

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:

FunctionDoes
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

3

Selection: IF and CASE

Syllabus 11.2

Selection is a choice between different paths of execution, based on whether a condition is TRUE or FALSE.

IF, ELSE and nested IF

pseudocode
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:

pseudocode
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:

pseudocode: nested IF
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:

Worked example: grade classification

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.

pseudocode
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.

Nested IF or CASE, which one?

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".


4

Iteration: FOR, WHILE, REPEAT UNTIL

Syllabus 11.2

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.

FOR · count-controlled

Use when you know in advance exactly how many times the loop must run.

WHILE · pre-condition

Checks the condition before each pass, so the body can run zero times if the condition is false to begin with.

REPEAT UNTIL · post-condition

Checks the condition after each pass, so the body always runs at least once.

Choosing between them

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.

Interactive: same task, three loop types
Reading names from the user
All three loops below carry out the same job: read 10 names one at a time. Switch between them to see exactly how the condition and the counter move.
for · count-controlled

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 UNTIL version
REPEAT
    OUTPUT "Enter a number between 1 and 100"
    INPUT num
UNTIL num >= 1 AND num <= 100
WHILE version
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

Temperature Monitor

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.

pseudocode
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.

Interactive: trace table
Trace the Temperature Monitor
A trace table is how you dry-run pseudocode by hand: one row per pass through the loop, one column per variable. Step through these five sample readings and watch the table build itself, exactly how you should build one on paper in the exam.

5

Arrays: 1D and 2D

Syllabus 10.2

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.
pseudocode: declaring a 1D array
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

pseudocode
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.

Worked example: StudentNames

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.

pseudocode
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.

pseudocode: declaring a 2D array
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:

Worked example: 5 students, 3 tests

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.

pseudocode
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
Interactive: 2D array indexing
Click a cell to see its index
This is TestScores, a 4 row by 4 column array. Click any cell to see exactly how it is written in pseudocode.
Click a cell above.

6

Array Algorithms: Linear Search & Bubble Sort

Syllabus 10.2

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.

Question

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

pseudocode
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.

pseudocode
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
Common bug to watch for

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.

pseudocode: sorting Scores into ascending order (and keeping CricketTeams matched)
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.

Interactive: search & sort visualiser
Watch linear search and bubble sort run
Six scores from the example above. Switch tabs to step through either algorithm one comparison at a time.
Press Step to begin.

7

Records and User-Defined Types

Syllabus 10.1

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:

pseudocode
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

pseudocode
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:

pseudocode
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
Records vs 2D arrays

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.


8

Abstract Data Types: Stacks, Queues, Linked Lists

Syllabus 10.4

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.

You will not be asked to write pseudocode for these

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.

Stack · LIFO

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).

Queue · FIFO

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.

Linked list

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

ADTArray-based implementation
StackA 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.
QueueA 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 listTwo 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.
Interactive: stack vs queue
Add and remove items, watch which one comes out
Switch between the two ADTs and add a few items, then remove one, to see LIFO and FIFO behave differently on exactly the same input order.

9

File Handling

Syllabus 10.3

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

StatementDoes
OPENFILE FileName FOR READOpens an existing file so its lines can be read
OPENFILE FileName FOR WRITECreates a new file (or empties an existing one) so lines can be written to it
OPENFILE FileName FOR APPENDOpens an existing file so new lines can be added to the end, without deleting what is already there
READFILE FileName, VariableReads the next line from the file into Variable
WRITEFILE FileName, ValueWrites Value as a new line in the file
EOF(FileName)Returns TRUE once every line has been read, "end of file"
CLOSEFILE FileNameCloses 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:

pseudocode: reading every line of a file
OPENFILE "Data.txt" FOR READ
WHILE EOF("Data.txt") = FALSE
    READFILE "Data.txt", OneLine
    OUTPUT OneLine
ENDWHILE
CLOSEFILE "Data.txt"
Worked example: stripping comments from a source file

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.

pseudocode
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.


10

Procedures and Functions

Syllabus 11.3

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.

PROCEDURE

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.

FUNCTION

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

TermMeaning
HeaderThe first line: PROCEDURE Name(...) or FUNCTION Name(...) RETURNS Type
InterfaceThe 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
ParameterThe placeholder name declared in the header, e.g. a in PROCEDURE Swap(BYREF a : INTEGER, BYREF b : INTEGER)
ArgumentThe actual value supplied when the block is called, e.g. the 5 and x in Swap(5, x)
Return valueThe 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.
pseudocode: why Swap needs BYREF
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.

Interactive: BYVAL vs BYREF
Call Swap(x, y) both ways
Watch what happens to the caller's own x and y after the same Swap procedure runs, once with BYVAL parameters, once with BYREF.

Worked example: a pure function

IsRA: is this a right-angled triangle?

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).

pseudocode
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.

Writing efficient pseudocode

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.


11

Worked Programs: Putting It Together

Sections 9-11

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

Question

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
pseudocode
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.

Bug patterns worth knowing

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

Question

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:

4444 4**4 4**4 4444
pseudocode
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.


12

Program Development Life Cycle

Syllabus 12.1

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:

1. Analysis

Understand and document exactly what the system must do, gathering requirements from the people who will use it.

2. Design

Plan the solution before coding it: data structures, algorithms, structure charts, interfaces.

3. Coding

Translate the design into working pseudocode/program code.

4. Testing

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.

Compare the three models
Waterfall, Iterative, or RAD?
Click a model to see how it arranges the five stages, and when it suits a project.
ModelHow it worksBenefitDrawback
WaterfallEach 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.
IterativeThe 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.

13

Program Design: Structure Charts & State-Transition Diagrams

Syllabus 12.2

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.

Structure chart for the library loan system (section 11)
Library System | +----------------+---+---+----------------+ | | | | OKToBorrow(sID) NewLoan(sID,bID) ReturnBook(sID,bID) CountLoans(sID) [outputs msg] [returns Success] [returns Success] [outputs msg]
ModuleData passed inData passed back
OKToBorrowsID (STRING)none, a procedure
NewLoansID, bID (STRING)Success (BOOLEAN)
ReturnBooksID, bID (STRING)Success (BOOLEAN)
CountLoanssID (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.

State-transition diagram for the login system (section 1)
(Locked) --[attempt entered, correct]--> (Granted) (Locked) --[attempt entered, wrong, attempts left]--> (Locked) (Locked) --[attempt entered, wrong, no attempts left]--> (Denied)

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.

When to reach for which diagram

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.


14

Testing and Maintenance

Syllabus 12.3

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 typeWhat it meansExample
Syntax errorThe 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 errorThe 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 errorThe 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

MethodWhat it involves
Dry runManually tracing the pseudocode by hand, line by line, using a trace table (section 4) to predict what it should output.
WalkthroughA structured, planned dry run performed in front of colleagues, so a team can review logic together before it is coded.
White-box testingTests 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 testingTests designed purely from what the program is supposed to do (its specification), with no knowledge of how it is coded internally.
StubA temporary, simplified placeholder for a module that has not been written yet, so the rest of the program can still be tested.
Integration testingTesting that separately-written modules work correctly once combined together.
Alpha testingTesting carried out in-house, by the development team or organisation, before any outside release.
Beta testingTesting carried out by a limited group of real, external users, using a near-final version, before general release.
Acceptance testingFormal 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:

Normal

Sensible, everyday data that a valid run of the program should clearly accept and process correctly.

Boundary (extreme)

Data sitting exactly on, or one either side of, the edge of what is valid, this is where off-by-one errors hide.

Abnormal (erroneous)

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.

Worked example: testing the range-validation loop

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 dataCategoryWhy
50NormalAn ordinary, clearly valid value well inside the range.
1 and 100BoundaryThe exact edges of the valid range, must both be accepted.
0 and 101BoundaryOne 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"AbnormalWildly invalid values (out of range, wrong type), the program must reject these without crashing.
Interactive: classify the test data
Sort each value into normal, boundary or abnormal
Still testing "enter a number between 1 and 100" from section 4. Click a value below, then click the bin you think it belongs in.
Normal
Boundary
Abnormal

Maintenance

Maintenance is everything that happens to a program after it is deployed and in use. The syllabus names three types:

Corrective

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).

Adaptive

Changing the program so it keeps working correctly as its environment changes, a new operating system, a changed file format, a new legal requirement.

Perfective

Improving the program beyond fixing faults, better performance, a clearer interface, new features the users have asked for.

Spot the error

Q
A student writes 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?
Show answer
Answer

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.

Q
A linear search sets 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?
Show answer
Answer

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.


Want the full AS Level 9618 course in one place?
Every chapter, worked examples and practice questions for Cambridge AS Level Computer Science, built to actually make sense.
Browse all notes

Stop wrestling with confusion.

Join thousands of students mastering Computer Science without the academic jargon.

From syntax to systems. We break down the hardest ideas in computer science so you can actually build things.

© 2026 Painless Programming. Built for students.
Scroll to Top