Lesson 8 of 10
Programming: Lesson 8

Subroutines: Procedures and Functions

Breaking programs into reusable, named blocks. This lesson covers defining and calling subroutines, passing parameters, returning values, local vs global scope and why modular code is easier to test and maintain.

55 minutes Python + C# toggle
Language: Saved automatically

Without subroutines, every program would be one long sequence of code - and if you needed to validate input in 10 different places, you would have to copy the same 5 lines 10 times. Change the validation rule once? Update it in 10 places. Miss one? Bug. Subroutines solve this: write the logic once, call it by name from anywhere. Every professional codebase is thousands of small, named pieces of logic. A program that is one long block is a program nobody can read, test or fix.

Think about it: Look back at Lesson 6's validation loop and Lesson 7's list search. Could both of those be turned into reusable functions? What parameters would they need? What would they return? Try sketching the function signatures before looking at the code examples.
Terms you need to know
Subroutine
A named, reusable block of code. Can be called multiple times from anywhere in the program.
Procedure
A subroutine that performs an action but does NOT return a value. Python: def with no return. C#: void method.
Function
A subroutine that computes and RETURNS a value. Python: def with return. C#: method with a return type.
Parameter
A variable in the function definition that receives the value passed in. Also called a formal parameter.
Argument
The actual value passed to a function when calling it. Also called an actual parameter.
Local scope
Variables declared inside a function - only accessible within that function. Destroyed when the function returns.
Global scope
Variables declared outside all functions - accessible throughout the program. Generally avoided in well-designed code.
Return value
The value a function sends back to the caller using the return keyword.
Procedures and functions

A subroutine must be defined (written) before it can be called (used). In Python, use def. In C#, declare a method with a return type (or void for procedures).

# Procedure (no return value) - just does an action
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")    # calling with argument "Alice"
greet("Bob")      # reuse the same procedure

# Function (returns a value)
def add(a, b):
    result = a + b
    return result

total = add(7, 3)        # total = 10
print(add(100, 200))    # 300
// Procedure (void = no return value)
static void Greet(string name)
{
    Console.WriteLine($"Hello, {name}!");
}

Greet("Alice");
Greet("Bob");

// Function (returns an int)
static int Add(int a, int b)
{
    int result = a + b;
    return result;
}

int total = Add(7, 3);       // total = 10
Console.WriteLine(Add(100, 200)); // 300
Where variables live

Variables created inside a function are local - they only exist while the function runs, and cannot be seen outside it. This is called encapsulation. It prevents functions from accidentally changing variables in other parts of the program.

# Local scope example
def calculate(x):
    local_var = x * 2    # local - only exists inside calculate()
    return local_var

print(calculate(5))    # 10
# print(local_var)    # NameError! local_var does not exist here

# Global variable
total = 0               # global - defined outside all functions
def add_to_total(n):
    global total          # must declare to modify a global inside a function
    total += n

add_to_total(10)
print(total)            # 10 (modified by the function)
// Local variables in C# methods
static int Calculate(int x)
{
    int localVar = x * 2;   // local - destroyed when method returns
    return localVar;
}
// localVar is not accessible here - only inside Calculate()

// Class-level field (similar to global) - avoid in exam code
static int total = 0;
static void AddToTotal(int n) { total += n; }
Why local scope matters

Local variables protect the rest of the program from accidental changes. Two functions can each have a local variable called count without interfering with each other. This is why modular programs are easier to debug - each function's variables are isolated and cannot corrupt other parts of the program.

Exam angle - procedure vs function

Examiners test the distinction: a procedure performs an action and does not return a value (Python: def with no return, C#: void). A function computes and returns a value (Python: def with return, C#: non-void return type). When asked to write a function that "finds the largest of two numbers", you MUST include a return statement - a procedure that only prints is wrong in this context.

Call Stack Visualiser
Function Call Stack
Step through a function call to see how the call stack grows and shrinks as functions are called and return.
def square(n):
    result = n * n
    return result
 
def main():
    x = 5
    y = square(x)
    print(y)
 
main()
Call stack (top = active)
Click "Next step" to start walking through the program...

Three Quick Challenges

Predict the output

Trace through the nested function calls carefully:

def triple(n):
    return n * 3

print(triple(triple(2)))
static int Triple(int n) => n * 3;
Console.WriteLine(Triple(Triple(2)));
Fill in the blank

Complete the function so it sends a value back to the caller:

def add(a, b): a + b
static int Add(int a, int b) { a + b; }
Spot the bug

What unexpected value does result hold?

def greet(name):
    print("Hello, " + name)

result = greet("Alice")
print(result)

Test yourself

1. What is the difference between a procedure and a function?

The key distinction is the return value. A function computes and returns a result (Python: return statement; C#: non-void return type). A procedure performs an action but sends no value back (Python: def with no return or return None; C#: void method).

2. What is the parameter in: def multiply(x, y): return x * y?

Parameters are the named variables in the function definition: x and y. When you call multiply(3, 4), the values 3 and 4 are the arguments. Parameters receive the argument values. The distinction: "parameter" = definition side; "argument" = call side.

3. A local variable inside a function is accessed outside it. What happens?

Local variables are destroyed when the function returns. Attempting to access them outside causes a NameError in Python (or a compile error in C#). This is intentional - it prevents functions from leaking internal state into the rest of the program.

4. What is the return value of: def cube(n): return n ** 3? What does cube(4) evaluate to?

n = 4. n ** 3 = 4 * 4 * 4 = 64. The return statement sends 64 back to the caller. cube(4) evaluates to 64. You could then use it as: print(cube(4)) or result = cube(4).

5. A function uses the global keyword. What does this allow?

Without global, assigning to a name inside a function creates a new local variable. The global keyword tells Python that the name refers to the variable defined in the outer (global) scope, allowing the function to modify it. Generally, modifying globals from functions is bad practice - return values are preferred.
Challenge question

A student writes a function to check if a number is prime. However, the function has a bug. Identify the bug, explain what incorrect output it produces, and write a corrected version.

def is_prime(n):
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

print(is_prime(1))   # should be False but...
The bug: The function returns True for n = 1. By definition, 1 is NOT a prime number (a prime has exactly two factors: 1 and itself; 1 has only one factor). The loop range(2, 1) is empty so it never runs, and the function falls through to return True.

Fix: Add a guard clause at the start: if n < 2: return False. A complete correct version:
def is_prime(n):
    if n < 2: return False
    for i in range(2, int(n**0.5) + 1): # only need to check up to sqrt(n)
        if n % i == 0: return False
    return True

The optimised version (checking only to sqrt(n)) runs much faster for large n, because if n has a factor larger than sqrt(n), it must also have one smaller than sqrt(n).
Printable Worksheets

Practice what you have learned

Three levelled worksheets. Download, print and complete offline.

Recall
Subroutine Structures
Label function definitions, identify parameters and return values, and distinguish procedures from functions.
Download
Apply
Writing Functions
Write functions to validate input, calculate statistics, check conditions and format output strings.
Download
Exam Style
Exam-Style Questions
Trace function calls, identify scope errors, write procedures and functions for described scenarios.
Download
Lesson 8 - Programming
Subroutines: Procedures and Functions
Starter activity
Show two versions of a grade classification program: (A) without functions - 30+ lines of repeated if/elif logic; (B) with a function - 8 lines of clean code. Ask: which is easier to read? Which is easier to fix if the grade boundaries change? This motivates why functions exist before any definition syntax is shown.
Lesson objectives
1
Define and call subroutines with and without parameters in Python and C#.
2
Distinguish between procedures (void) and functions (return value).
3
Explain the difference between parameters and arguments.
4
Describe local scope and explain why local variables cannot be accessed outside their function.
5
Use the global keyword in Python and explain when it is needed.
Key vocabulary
subroutineprocedurefunctionparameterargumentreturn valuelocal scopeglobal scopecall stackvoiddef (Python)static (C#)
Discussion questions
Why is it better to break a large program into subroutines rather than writing it as one block? Give a real example where this makes maintenance easier.
What is the difference between a parameter and an argument? Why does this distinction matter when reading exam questions?
Why is local scope a feature rather than a limitation? What would go wrong if all variables were global?
Exit tickets
Write a function get_grade(score) that returns "Pass" if score >= 50, otherwise "Fail". [3 marks]
What is the output of: def f(x): return x*2; print(f(3) + f(4))? Show your working. [2 marks]
Explain what "local scope" means and why it is useful. [3 marks]
Homework suggestion
Refactor the grade classifier program from Lesson 4 into a modular program: (1) a function get_grade(score) that returns the grade letter, (2) a function is_valid_score(score) that returns True if 0-100, (3) a procedure display_result(name, grade) that prints the formatted result, (4) a main procedure that ties it all together. Test with at least 5 different inputs.