Subprograms, Scope and Debugging
Why Break Code into Subprograms?
As programs grow longer, keeping everything in one flat block of code becomes unmanageable. A subprogram is a named block of code that does one clearly defined job. You call it by name whenever that job needs doing, rather than copying the code every time. Python supports two types of subprogram: procedures and functions. The specification is precise about the distinction, so keep the two terms apart in your answers.
A function MUST return a value using return. A procedure does NOT return a value. Both may take parameters. Writing “a function performs a task” without mentioning the return value is an incomplete definition.
Procedures in Python
A procedure is defined with def, may take parameters, performs its task, and then simply ends. It does not hand anything back to the line that called it.
def print_greeting(name):
print("Hello,", name, "- welcome to the system!")
# Calling the procedure
print_greeting("Priya")
print_greeting("Ali")
The procedure executes, produces output, and exits. The calling code receives nothing back. Good uses for procedures: printing a menu, displaying a result, writing a line to a file, sending a notification.
Functions in Python
A function is also defined with def, but it uses return to send a result back. The returned value replaces the function call in the expression where it appears.
def calculate_area(length, width):
area = length * width
return area
# Calling the function and using the returned value
room_area = calculate_area(5, 4)
print("Area:", room_area, "square metres")
# The return value can also be used directly in an expression:
if calculate_area(3, 3) > 8:
print("Room is large enough")
A function that checks if a student has passed (score ≥ 50) and returns a Boolean:
def has_passed(score):
if score >= 50:
return True
else:
return False
result = has_passed(72)
print(result) # True
result2 = has_passed(40)
print(result2) # False
Predict the output: 72 ≥ 50 so the first call returns True. 40 < 50 so the second returns False. The function’s return value is captured in a variable and then printed. This predict-before-run habit is essential for trace table questions.
Parameters
Parameters are the input variables listed in the subprogram’s definition. When you call the subprogram, you pass arguments — the actual values — which are assigned to the parameter names. The order must match: the first argument goes to the first parameter, and so on.
def greet_student(first_name, year_group):
print("Welcome, Year", year_group, "student", first_name)
greet_student("Beth", 10) # first_name = "Beth", year_group = 10
Local and Global Variables — Scope
Scope describes which parts of a program can read or change a variable. A local variable is declared inside a def block. It is created when the function is called and destroyed when it returns. Nothing outside the function can access it.
def calculate_tax(price):
tax_rate = 0.20 # local variable
tax = price * tax_rate
return tax
# tax_rate and tax do NOT exist out here
print(calculate_tax(100)) # 20.0
A global variable is declared at module level (outside all functions). It can be read anywhere in the program. The Python subset used in the exam does not include a global keyword. Instead, if a subprogram needs to change a value, it should take the value in as a parameter and return the new value, which the caller then assigns back to the variable, e.g. total = add_score(total, 5).
Local variables reduce the risk of bugs. If a global variable called score is shared across ten functions, any one of them could accidentally overwrite it. A bug that corrupts the score could be anywhere in the program — finding it means checking all ten functions. If score is instead a local parameter, only that function can change it. The problem is isolated immediately.
Use global variables only for values that genuinely need to be shared across the whole program — for example, a configuration setting loaded once at start-up, or a game’s running total score that every function needs to update.
Built-in and Library Subprograms
Python comes with many built-in subprograms that are always available: print(), input(), len(), int(), float(), str(), range(). These are functions — most of them return a value. Python also has an extensive standard library of library subprograms that must be imported before use, such as import math and then math.floor(7.8), which returns 7. The PLS lists math.ceil(), math.floor() and math.pi as the library subprograms and constants you need to know.
Types of Program Error
When a program fails, the error falls into one of three categories. Understanding which type of error you are dealing with determines where to look for the fix.
A syntax error occurs when the code violates the rules of Python. The program will not run at all. Python reports the line number and type of problem. Common causes: missing colon at the end of an if or def line, misspelled keyword, unmatched brackets.
A runtime error occurs during execution. The syntax is correct, so Python starts running the program — but something goes wrong mid-execution. Examples: dividing by zero (ZeroDivisionError), using an index beyond the end of a list (IndexError), trying to convert “hello” to an integer (ValueError).
A logic error is the most difficult to find. The program runs without crashing — but it produces the wrong answer. Examples: using > when the condition should be >=, updating a total inside a loop but printing it inside the loop instead of after it, or initialising an accumulator to 1 instead of 0.
Syntax error: program won’t run at all — Python language rules broken. Runtime error: program starts but crashes during execution — impossible operation attempted. Logic error: program runs to completion but produces the wrong output — the algorithm itself is flawed. Only a logic error can be invisible to Python — the other two produce explicit error messages.
Test Data — Normal, Boundary and Erroneous
Testing requires choosing test data that exercises different parts of the program. Three categories of test data cover the essential cases for any input that must lie within a range.
Normal test data uses typical values well within the valid range. For an age input (0 to 120), normal data might be 25, 45, 67. Boundary test data (also called edge data) tests the extreme limits: for the range 0–120, test 0, 1, 119, and 120. Erroneous test data is deliberately invalid: -1, 121, or “hello” for an age field.
Trace the output of the following function for the call classify(73):
def classify(score):
if score >= 80:
return "Distinction"
elif score >= 60:
return "Merit"
else:
return "Pass"
result = classify(73)
print(result)
Trace: score = 73. Is 73 ≥ 80? No. Is 73 ≥ 60? Yes → return “Merit”. result = “Merit”. Output: Merit. When tracing a function, substitute the argument value for the parameter name and follow each condition in order.