🔒
Pro Content
Subscribe to access all 69 OCR H446 A Level lessons.
£7.99/month
or £59/year
Subscribe now →
🔒 Pro · Component 2 · 2.2.1 Problem Solving & Programming
2.2.1f Modular Design and Standard Algorithms
OCR H446 · A Level Computer Science · ~20 min read
Notes
Video
Slides
Worksheet
Quiz

Modular Programming

Modular programming decomposes a large program into smaller, self-contained modules (functions/subroutines/procedures). Each module performs a single well-defined task and can be developed, tested, and maintained independently.

Functions vs Procedures

FunctionProcedure
Returns a value?Yes — uses returnNo (performs actions only)
Called as…Expression: x ← square(5)Statement: print_header()
Examplefunction area(r) → returns πr²procedure draw_border() → prints "======"

Parameters and Arguments

Parameters are variables defined in the subroutine header — placeholders. Arguments are the actual values passed when calling.

function power(base, exp)    // base, exp = parameters
    result ← 1
    for i ← 1 to exp
        result ← result * base
    next i
    return result
endfunction

answer ← power(2, 8)     // 2 and 8 = arguments  → answer = 256

Variable Scope

Local variables exist only within the subroutine where they're declared. They are created when the subroutine is called and destroyed when it returns. Global variables are accessible from anywhere in the program. Excessive global variables create hidden dependencies between modules — a design smell.

Black Box Principle

A module's internal implementation should be hidden from the rest of the program. Callers only need to know the interface (what inputs it takes, what it returns) — not how it works internally. This is information hiding / encapsulation. Changing the internal implementation doesn't break code that uses it, as long as the interface stays the same.

Advantages of Modular Design

  • Easier to develop: large teams can work on different modules simultaneously
  • Easier to test: each module can be unit-tested in isolation
  • Reusability: modules can be reused in other programs or called multiple times
  • Maintainability: a bug is localised to one module; fixing it doesn't affect others
  • Readability: well-named functions make code self-documenting

Coupling and Cohesion

Cohesion measures how closely related the elements within a module are. High cohesion is desirable — each module does one thing well. Coupling measures how dependent modules are on each other. Low coupling is desirable — modules are as independent as possible. Good design aim: high cohesion, low coupling.

Library Routines

Programming languages provide standard libraries — collections of pre-written, pre-tested functions for common tasks. Using library routines saves time, reduces bugs, and leverages optimised code.

Python LibraryCommon functions
mathmath.sqrt(), math.floor(), math.ceil(), math.log(), math.pi
randomrandom.random(), random.randint(a,b), random.choice()
strings.upper(), s.lower(), s.split(), s.strip(), s.replace()
collectionsdeque, Counter, defaultdict
osos.path.exists(), os.listdir(), os.getcwd()

Standard Algorithms: Sorting

Insertion Sort

Builds the sorted array one element at a time by inserting each new element into its correct position among already-sorted elements.

function insertion_sort(arr)
    for i ← 1 to length(arr) - 1
        key ← arr[i]
        j ← i - 1
        while j >= 0 AND arr[j] > key
            arr[j+1] ← arr[j]     // shift right
            j ← j - 1
        endwhile
        arr[j+1] ← key            // insert key in correct position
    next i
    return arr
endfunction

// Example: [5, 2, 4, 1, 3]
// i=1: key=2, shift 5 right → [5,5,4,1,3] → insert 2 → [2,5,4,1,3]
// i=2: key=4, shift 5 → [2,4,5,1,3]
// i=3: key=1, shift 5,4,2 → insert 1 → [1,2,4,5,3]
// i=4: key=3, shift 5,4 → insert 3 → [1,2,3,4,5] ✓

Time complexity: O(n) best case (already sorted), O(n²) worst/average. Space: O(1) — in-place. Stable sort. Good for small arrays or nearly-sorted data.

Merge Sort

Classic divide and conquer: split the array in half recursively until single elements, then merge sorted halves.

function merge_sort(arr)
    if length(arr) <= 1
        return arr                 // base case
    endif
    mid ← length(arr) DIV 2
    left ← merge_sort(arr[0:mid])
    right ← merge_sort(arr[mid:])
    return merge(left, right)
endfunction

function merge(left, right)
    result ← []
    i ← 0
    j ← 0
    while i < length(left) AND j < length(right)
        if left[i] <= right[j] then
            append left[i] to result
            i ← i + 1
        else
            append right[j] to result
            j ← j + 1
        endif
    endwhile
    append remaining elements of left or right to result
    return result
endfunction

Time complexity: O(n log n) in all cases. Space: O(n) — not in-place (needs extra memory for merging). Stable sort. Best for large datasets where consistent performance is needed.

Quicksort

Choose a pivot element; partition the array so all elements less than pivot are left, greater are right; recursively sort each partition.

function quicksort(arr, low, high)
    if low < high then
        pivot_pos ← partition(arr, low, high)
        quicksort(arr, low, pivot_pos - 1)
        quicksort(arr, pivot_pos + 1, high)
    endif
endfunction

function partition(arr, low, high)
    pivot ← arr[high]      // last element as pivot
    i ← low - 1
    for j ← low to high - 1
        if arr[j] <= pivot then
            i ← i + 1
            swap arr[i] and arr[j]
        endif
    next j
    swap arr[i+1] and arr[high]   // place pivot in correct position
    return i + 1
endfunction

Time complexity: O(n log n) average, O(n²) worst (sorted input with last-element pivot). Space: O(log n) average (recursive call stack). In-place. Not stable. Often faster than merge sort in practice due to cache efficiency.

Algorithm Comparison Summary

AlgorithmBestAverageWorstSpaceStable?
Bubble SortO(n)O(n²)O(n²)O(1)Yes
Insertion SortO(n)O(n²)O(n²)O(1)Yes
Merge SortO(n log n)O(n log n)O(n log n)O(n)Yes
Quick SortO(n log n)O(n log n)O(n²)O(log n)No
Binary SearchO(1)O(log n)O(log n)O(1)N/A
Exam tip: "Stable" sort preserves the relative order of equal elements. Merge sort is stable; quicksort is not. When asked to choose a sorting algorithm for a given scenario: if stable sort needed → merge sort or insertion; if memory limited → quicksort (O(log n) space) or insertion (O(1)); for nearly-sorted data → insertion sort.
Exam tip: Modular design exam answers should always mention: testability (test each module independently), reusability (use in multiple programs), maintainability (bugs isolated to one module), and team development (parallel working).
✓ Notes completed!
Video coming soon
Click to advance · Arrow keys also work
Click slide or press arrow keys to navigate

Worksheet — 2.2.1f Modular Design & Standard Algorithms

8 questions · 24 marks · instantly marked

Q1Give four advantages of modular programming.[4 marks]
✓ Mark scheme
Any 4 of: Modules can be tested independently (unit testing) [1]; modules can be reused in other programs, avoiding code duplication [1]; large teams can work on different modules simultaneously (parallel development) [1]; bugs are localised to one module, making maintenance easier [1]; well-named modules make code self-documenting and easier to read [1]; modules can be replaced/updated without affecting other parts (if interface unchanged) [1].
Q2Explain the difference between a function and a procedure. Give an example of each.[3 marks]
✓ Mark scheme
Function: returns a value using a return statement; called as part of an expression [1]. Example: function square(n) returns n*n — called as result ← square(5) [0.5]. Procedure: performs actions but does not return a value; called as a statement [1]. Example: procedure print_header() prints "===Header===" — called as print_header() [0.5].
Q3What is the 'black box principle' in modular programming? Why is it important?[3 marks]
✓ Mark scheme
The black box principle (information hiding): a module's internal implementation is hidden from the rest of the program [1]. Callers only need to know the interface — what inputs it takes and what it returns — not how it works internally [1]. Important because: if the implementation is later changed (e.g. more efficient algorithm), code that calls the module doesn't need to be changed as long as the interface remains the same. Reduces coupling between modules [1].
Q4Trace insertion sort on the array [4, 2, 7, 1, 5] and show the state of the array after each pass (each value of i).[4 marks]
✓ Mark scheme
i=1: key=2. arr[0]=4 > 2, shift right. Insert 2 at index 0 → [2, 4, 7, 1, 5] [1].
i=2: key=7. arr[1]=4 ≤ 7. No shifts. Insert 7 at index 2 → [2, 4, 7, 1, 5] [0.5].
i=3: key=1. Shift 7, 4, 2 right. Insert 1 at index 0 → [1, 2, 4, 7, 5] [1].
i=4: key=5. arr[3]=7 > 5 → shift. arr[2]=4 ≤ 5 → stop. Insert 5 at index 3 → [1, 2, 4, 5, 7] [1].
Final: [1, 2, 4, 5, 7] [0.5].
Q5Explain how merge sort uses the divide and conquer approach. What is its time complexity and why?[4 marks]
✓ Mark scheme
Divide: the array is split in half repeatedly until each sub-array has 0 or 1 elements (base case — trivially sorted) [1]. Conquer: single-element arrays are base cases (no further recursion needed) [0.5]. Combine: sorted sub-arrays are merged pairwise — comparing front elements and taking the smaller until all elements are placed [1]. Time complexity: O(n log n) in all cases [0.5]. The log n factor comes from the number of levels of division (halving produces log n levels); at each level, merging all elements takes O(n) total work; therefore log n × O(n) = O(n log n) [1].
Q6Compare merge sort and quicksort. When would you prefer each?[4 marks]
✓ Mark scheme
Merge sort: O(n log n) all cases; stable sort; O(n) extra space needed for merging; consistent performance. Quicksort: O(n log n) average; O(n²) worst case (bad pivot, e.g. sorted input); not stable; O(log n) extra space (call stack); often faster in practice due to cache efficiency and lower constant factors [2 — award 1 for each sort's key characteristics]. Use merge sort: when stable sort required (preserving order of equal elements); when worst-case guarantee needed; external sorting (larger-than-memory data) [1]. Use quicksort: when memory is limited (O(log n) vs O(n)); often faster in practice for average cases; when data isn't nearly sorted [1].
Q7Explain coupling and cohesion. What is the desired design goal?[3 marks]
✓ Mark scheme
Cohesion: how closely related the elements within a single module are — high cohesion means a module has one clear, focused responsibility [1]. Coupling: how dependent modules are on each other — low coupling means modules are relatively independent, interacting only through well-defined interfaces [1]. Design goal: high cohesion AND low coupling. High cohesion ensures each module is focused; low coupling ensures modules can be developed, tested, and modified independently without cascading side effects [1].
Q8State the difference between local and global variables. Why are excessive global variables considered bad practice?[3 marks]
✓ Mark scheme
Local variables: declared inside a subroutine; only accessible within that subroutine; created when subroutine is called, destroyed when it returns [1]. Global variables: declared outside all subroutines; accessible from anywhere in the program [0.5]. Excessive global variables are bad practice because: any part of the program can modify them, creating hidden dependencies — if a module changes a global variable, it affects all other modules that read it [1]; this makes debugging difficult (hard to trace where a value changed); it increases coupling between modules, undermining the benefits of modular design [0.5].
Topic Quiz
1 of 15
You scored
out of 15
🎯

Mini Test — 2.2.1f Modular Design & Algorithms

  • 10 questions · 10 marks · 10 minutes
  • 5 MCQ + 5 short answer
Card 1 of 15
Click to reveal
🎉
Complete!
TermDefinition
← 2.2.1e Writing & Tracing Algorithms 2.2.1 Problem Solving & Programming Next: 2.3.1a Stacks, Queues & Complexity →