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.
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.
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
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; }
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.
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.
Three Quick Challenges
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)));
Complete the function so it sends a value back to the caller:
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?
2. What is the parameter in: def multiply(x, y): return x * y?
3. A local variable inside a function is accessed outside it. What happens?
4. What is the return value of: def cube(n): return n ** 3? What does cube(4) evaluate to?
5. A function uses the global keyword. What does this allow?
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...
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 TrueThe 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).
Practice what you have learned
Three levelled worksheets. Download, print and complete offline.