Programming techniques: small components, clear state
Connect control structures to modular programs, distinguish local and global state, reason precisely about parameters and use debugging evidence. Covers OCR H446 Paper 2 section 2.2.1(a, c–e).
Content owner: Michael Print · Written for A-Level learners · Checked against official specifications
The short answer
Sequence executes steps in order; branching chooses a path using a condition; iteration repeats a group of steps. Modularity groups related work into components with clear interfaces so they can be understood, reused and tested separately.
A variable's scope determines where its name can be accessed. Parameter passing determines how a subprogram receives arguments and whether changes can affect the caller. Explain the rules for the language or pseudocode being used.
Identify the three programming constructs in a working algorithm.
Trace local and global names without confusing equal names with shared state.
Distinguish functions, procedures and value/reference parameter models.
Use breakpoints, stepping and variable inspection to locate a logic error.
Give each component one clear responsibility
This original program counts scores at or above a threshold, then displays the count. The counting component accepts a list of numeric scores and a numeric threshold.
An empty list is valid and produces zero.
If data arrives as untrusted text, the caller must validate it before using this component.
Separate calculation from reportingPython
PASS_MARK = 60
total = 100
def count_passes(scores, threshold):
total = 0
for score in scores:
if score >= threshold:
total += 1
return total
def show_report(count):
print("Passes:", count)
result = count_passes([35, 70, 80], PASS_MARK)
show_report(result)
print(total)
Constructs used by count_passes
Construct
Where it appears
Purpose
Sequence
Initialise total before looping; return after looping
Start from zero and return the final count
Iteration
for score in scores
Visit each score once
Branching
if score >= threshold
Increment only for a passing score
A counted loop suits a known collection. A condition-controlled loop suits a task such as reading attempts until a correct answer is entered, provided its condition can eventually become false. Nesting puts one construct inside another: this program evaluates a branch on each loop iteration.
Worked example
Trace a call, its local state and its result
Bind the parameters:scores refers to [35, 70, 80]; threshold receives the argument 60.
Create the local count: the assignment inside count_passes() creates its own total, starting at zero.
Visit each score: 35 does not pass, so total stays 0; 70 passes, so it becomes 1; 80 passes, so it becomes 2.
Return the value:return total ends the call with result 2; the main program stores it in result.
Report and inspect: the output is Passes: 2, then 100. The module-level total was not changed by assigning the function's local name.
Equal names can belong to different scopes
Module → PASS_MARK and total→
PASS_MARK is 60; the main program’s total remains 100. These names are global to this module.
Function call → scores, threshold and total→
The parameters and count are local to this call. Its total starts at zero and reaches 2.
Return → result→
The count 2 becomes the caller’s result. Returning it does not rebind the module-level total.
Passing the threshold makes the dependency explicit: the same function can use 50 or 70 without changing its body.
Functions, procedures and scope
Calculate a value or perform an action?
Function → value for the caller
count_passes() calculates and returns a count. Its caller can store, test or reuse that value.
Procedure → task or side effect
show_report() displays a message. It serves the examination procedure role without returning that message as a result value.
These are the usual examination roles. Python uses def for both.
Python defines both using def. A Python call that reaches the end without an explicit return expression still returns None. Printing is a side effect; it does not return the printed message to the caller.
Keep state and dependencies visible
Local names reduce accidental interference between components. A module-level global can hold shared configuration, but changing shared global state makes a component depend on earlier calls.
In Python, rebinding a module global inside a function requires global. Passing a parameter and returning a result often makes the state easier to follow.
Test each responsibility
Modularity supports focused testing: test the count without reading console output, then test the display separately. It also has costs: interfaces must agree, too many tiny components can obscure the flow, and a bug in a shared component affects every caller.
State the parameter model before tracing a change
Two conventional parameter-passing models
Model
What the subprogram receives
Effect of assigning its parameter
By value
A copy of the argument value
Does not reassign the caller's variable
By reference
Access to the caller's variable/storage location
Can change the caller's variable
For reference-valued objects, copying a reference by value can leave two names referring to the same mutable object. A by-value call does not automatically copy all the data inside that object.
This teaching pseudocode uses explicit VALUE and REFERENCE annotations to define its rules. They do not claim an OCR pseudocode syntax requirement.
Scalar value and reference parameters under stated pseudocode rulesTeaching pseudocode
PROCEDURE addByValue(VALUE number)
number <- number + 1
ENDPROCEDURE
PROCEDURE addByReference(REFERENCE number)
number <- number + 1
ENDPROCEDURE
score <- 30
addByValue(score)
OUTPUT score // 30
addByReference(score)
OUTPUT score // 31
The value parameter's private number becomes 31, but the caller's score stays 30. The reference parameter accesses the caller's score, so its later assignment changes that score to 31.
Worked example
Python shares objects; rebinding is different from mutation
Trace one immutable integer and one mutable listPython
At the call: the local parameter names refer to the same integer and list objects supplied by the caller.
Rebind score:score + 1 produces an integer value 31. Assigning it binds the local parameter to that value; the caller's score remains 30.
Mutate the list:append("B") changes the shared list itself. The caller can see ["A", "B"].
Rebind labels: assigning ["replacement"] makes only the local parameter refer to a different list. It does not replace the caller's binding.
The output is 30 then ['A', 'B']. Python's argument model is often called object sharing, or passing object references by value.
Assigning to a parameter cannot traditionally reassign the caller's variable as a by-reference parameter could. Trace rebinding separately from mutation of the shared object.
Use an IDE to gather evidence about a fault
An integrated development environment brings tools such as a source editor, syntax highlighting, completion, execution and a debugger together. Error indicators can reveal syntax problems. A program that runs and produces the wrong result needs a logic investigation; highlighting cannot prove it correct.
A deliberately faulty loop: the last score is skippedPython
def count_passes_bug(scores, threshold):
count = 0
for i in range(len(scores) - 1):
if scores[i] >= threshold:
count += 1
return count
print(count_passes_bug([35, 70, 80], 60))
Worked example
Debug the incorrect count rather than guessing a replacement
State expected and actual results: two scores meet 60, but the program prints 1. This is a reproducible logic error.
Pause at the loop: set a breakpoint before or on its first iteration. Inspect len(scores) (3) and count (0).
Step and watch: the debugger visits i = 0 and i = 1. Score 70 increases count to 1. There is no visit to i = 2.
Explain the cause:range(3 - 1) gives 0 and 1 because Python excludes the upper endpoint.
Repair and retest: use range(len(scores)), or iterate directly with for score in scores. Check the original list, [80] (1), [] (0) and [59, 60] (1).
Choose the debugger tool for your question
Variable watch: track values while execution is paused.
Step into: enter a called function.
Step over: execute the call while remaining in the current frame.
Call stack: inspect the active calls to see where a function was invoked.
Test results check observed behaviour; the debugger explains how it arose.
Try it yourself
These five original questions total 19 marks. Marking guidance is indicative and independently written.
Question 1
4 marks
For count_passes([60, 59, 90], 60), state the returned value. Identify one instance each of sequence, iteration and branching in the function.
Show solution and marking guidance+
Indicative answer:
The result is 2 (1).
Sequence: initialise the count before processing/return after processing (1).
Iteration: the for loop visits the scores (1).
Branching: the if increments only when a score meets 60 (1).
The boundary score 60 counts as a pass.
Question 2
4 marks
Give both output lines from the complete score program. Explain why the final line is not the count returned by the function.
Show solution and marking guidance+
Indicative answer:
First output: Passes: 2 (1).
Second output: 100 (1).
The function's total is local to that call (1).
It does not rebind the module-level total used by the final print (1).
Equal spelling does not make them one variable.
Question 3
4 marks
Give both output lines from change() and its main program. Explain why appending “B” affects the caller's list but assigning “replacement” does not replace that list.
Show solution and marking guidance+
Indicative answer:
First output: 30 (1).
Second output: ['A', 'B'] (1).
append mutates the list object also referenced by the caller (1).
The new-list assignment rebinds only the local parameter. The caller keeps the mutated original list (1).
Question 4
4 marks
Describe a debugger action that would expose the faulty loop's missing last item, explain the incorrect bound, supply a correction and give a boundary test with its expected result.
Show solution and marking guidance+
Indicative answer:
Breakpoint at the loop/body; step through while inspecting i, score and count (1).
The observed indices stop at 1: range endpoint 2 is excluded, skipping index 2 (1).
Use range(len(scores)) or equivalent direct iteration (1).
Test [80] with threshold 60; the expected count is 1 (1).
Accept another last-item-only pass test whose expected value exposes the same error.
Question 5
3 marks
Distinguish the roles of count_passes() and show_report(). Explain one benefit of keeping them separate.
Show solution and marking guidance+
Indicative answer:
The calculation function returns a count for its caller to use (1).
The reporting procedure performs a display action (1).
The count can be tested/reused independently of console output; alternatively, change reporting without rewriting calculation (1).
Python's implicit None return is compatible with this procedure role.
Specification links and scope
Checked against OCR H446 version 3.0 (2026), 2.2.1(a, c–e): constructs, global/local variables, modularity, functions/procedures, parameters and IDE use. Recursion in (b) and object-oriented techniques in (f) need their own resources.
The parameter pseudocode defines its own annotations; the Python examples use Python's documented object model.
These are independently written explanations and practice questions. CompSciTutoring.co.uk is not affiliated with or endorsed by an examination board. The marking guidance is indicative; always check the syllabus for your examination year.