Paper 2 Programming Fundamentals Answers

These answers correspond to Paper 2 Programming Fundamentals Drills.

Verification note: every model code block in this file has been executed locally after the 2026-07-10 revision.

Answer 1: Grade Function

def grade_from_mark(mark):
    if mark >= 75:
        return "A"
    if mark >= 60:
        return "B"
    if mark >= 50:
        return "C"
    return "U"
 
 
print(grade_from_mark(75))
print(grade_from_mark(74))
print(grade_from_mark(49))

Precondition: mark is an integer from 0 to 100 inclusive, so extra validation is not required in this question.

Expected output:

A
B
U

Mark points:

  • uses a function with parameter mark;
  • checks the highest boundary first;
  • uses >= so boundary values are included;
  • returns the correct grade string for each range;
  • returns "U" for marks below 50;
  • shows the required test outputs.

Common weak answer:

  • checking mark >= 50 first, which would incorrectly return "C" for 75.

Answer 2: List Average

def average(values):
    if len(values) == 0:
        return 0
    return sum(values) / len(values)
 
 
print(average([6, 9, 12]))
print(average([]))

Expected output:

9.0
0

Mark points:

  • checks for an empty list before dividing;
  • calculates the sum of the list;
  • divides by the number of values;
  • returns the calculated mean;
  • shows both required test outputs.

Common weak answer:

  • dividing by len(values) before checking for an empty list, causing division by zero.

Answer 3: 2D List Row Totals

def row_totals(grid):
    totals = []
    for row in grid:
        row_total = 0
        for value in row:
            row_total = row_total + value
        totals.append(row_total)
    return totals
 
 
print(row_totals([[3, 4, 5], [10, 0, 2], [7, 8, 9]]))

Expected output:

[12, 12, 24]

Mark points:

  • creates a result list;
  • loops through each row;
  • totals values within the current row;
  • appends one total per row;
  • returns the result list;
  • produces the correct row totals;
  • shows the required output.

Common weak answer:

  • returning one grand total instead of a separate total for each row.

Answer 4: String Vowel Count

def count_vowels(text):
    count = 0
    for character in text.lower():
        if character in "aeiou":
            count = count + 1
    return count
 
 
print(count_vowels("A quiet room"))
print(count_vowels("SKY"))

Expected output:

6
0

Mark points:

  • initialises a counter;
  • handles uppercase and lowercase input;
  • loops through every character;
  • checks membership in aeiou;
  • increments only for vowels;
  • shows both required outputs.

Common weak answer:

  • checking only lowercase vowels without converting the input, so "A" would be missed.

Answer 5: Recursive Factorial

def factorial(n):
    if n == 0 or n == 1:
        return 1
    return n * factorial(n - 1)
 
 
print(factorial(0))
print(factorial(1))
print(factorial(5))

Expected output:

1
1
120

Mark points:

  • defines a recursive function;
  • includes a base case for 0 or 1;
  • returns 1 in the base case;
  • calls factorial(n - 1) in the recursive case;
  • multiplies by n;
  • produces correct output for base cases;
  • produces correct output for the recursive case factorial(5).

Common weak answer:

  • missing the base case, causing infinite recursion.

Answer 6: Read Lines

def count_at_least(filename, threshold):
    count = 0
    with open(filename, "r") as file:
        for line in file:
            mark = int(line.strip())
            if mark >= threshold:
                count = count + 1
    return count
 
 
print(count_at_least("marks.txt", 60))

Expected output:

2

Mark points:

  • opens the file for reading;
  • loops through all lines;
  • strips newline characters;
  • converts each mark to integer;
  • compares with threshold using >=;
  • counts matching records;
  • returns and prints the correct count.

Common weak answer:

  • comparing marks as strings instead of integers.

Answer 7: Write Results

def write_passes(records, filename):
    with open(filename, "w") as file:
        for record in records:
            name = record[0]
            mark = record[1]
            if mark >= 50:
                file.write(name + "," + str(mark) + "\n")
 
 
records = [["Asha", 74], ["Ben", 49], ["Chen", 82], ["Divya", 50]]
write_passes(records, "passes.txt")
 
with open("passes.txt", "r") as file:
    print(file.read(), end="")

Expected output:

Asha,74
Chen,82
Divya,50

Mark points:

  • opens the output file in write mode;
  • loops through all records;
  • selects records with marks at least 50;
  • converts the mark to string before writing;
  • writes one record per line in the requested format;
  • prints the resulting file contents.

Common weak answer:

  • omitting the newline, causing all records to appear on one line.

Answer 8: Student Code Normalisation

def normalise_student_code(code):
    cleaned = code.strip().upper()
    if len(cleaned) < 2:
        return "INVALID"
    if cleaned[0] != "S":
        return "INVALID"
    for character in cleaned[1:]:
        if character < "0" or character > "9":
            return "INVALID"
    return cleaned
 
 
print(normalise_student_code(" s1042 "))
print(normalise_student_code("T204"))
print(normalise_student_code(" s10a "))

Expected output:

S1042
INVALID
INVALID

Mark points:

  • defines normalise_student_code(code);
  • strips leading and trailing spaces;
  • converts letters to uppercase;
  • checks that the cleaned code is long enough before indexing;
  • checks that the first character is "S";
  • checks every remaining character is a digit;
  • returns "INVALID" for invalid inputs;
  • produces all required outputs.

Common weak answer:

  • checking only the first character and forgetting to reject a code such as "S10A".

Answer 9: First Index Below Limit

def first_below(values, limit):
    for index in range(len(values)):
        if values[index] < limit:
            return index
    return -1
 
 
print(first_below([80, 75, 49, 60], 50))
print(first_below([55, 60], 50))
print(first_below([49], 50))

Expected output:

2
-1
0

Mark points:

  • defines first_below(values, limit);
  • loops through valid indexes of the list;
  • accesses each value using the current index;
  • compares each value with limit using <;
  • returns the index immediately when the first matching value is found;
  • returns -1 when no value is below the limit;
  • handles the boundary case where the first item matches;
  • produces all required outputs.

Common weak answer:

  • returning the value 49 instead of its index 2.

Answer 10: Function Decomposition

def is_pass(mark):
    return mark >= 50
 
 
def student_result(name, mark):
    if is_pass(mark):
        return name + ": pass"
    return name + ": resit"
 
 
print(student_result("Asha", 50))
print(student_result("Ben", 49))
print(student_result("Chen", 75))

Expected output:

Asha: pass
Ben: resit
Chen: pass

The second function calls the Boolean helper is_pass(mark) instead of repeating the pass/fail condition independently.

Mark points:

  • defines is_pass(mark);
  • returns a Boolean result from is_pass;
  • uses the boundary condition mark >= 50;
  • defines student_result(name, mark);
  • calls is_pass(mark) inside student_result;
  • returns the correct "name: pass" or "name: resit" string;
  • produces all required outputs, including the boundary case 50.

Common weak answer:

  • duplicating the condition inside student_result and never calling the helper function.