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.
| Function | Procedure | |
|---|---|---|
| Returns a value? | Yes — uses return | No (performs actions only) |
| Called as… | Expression: x ← square(5) | Statement: print_header() |
| Example | function area(r) → returns πr² | procedure draw_border() → prints "======" |
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
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.
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.
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.
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 Library | Common functions |
|---|---|
| math | math.sqrt(), math.floor(), math.ceil(), math.log(), math.pi |
| random | random.random(), random.randint(a,b), random.choice() |
| string | s.upper(), s.lower(), s.split(), s.strip(), s.replace() |
| collections | deque, Counter, defaultdict |
| os | os.path.exists(), os.listdir(), os.getcwd() |
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.
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.
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 | Best | Average | Worst | Space | Stable? |
|---|---|---|---|---|---|
| Bubble Sort | O(n) | O(n²) | O(n²) | O(1) | Yes |
| Insertion Sort | O(n) | O(n²) | O(n²) | O(1) | Yes |
| Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes |
| Quick Sort | O(n log n) | O(n log n) | O(n²) | O(log n) | No |
| Binary Search | O(1) | O(log n) | O(log n) | O(1) | N/A |
8 questions · 24 marks · instantly marked
| Term | Definition |
|---|