PAPER 2 REVISION
2.1 — Elements of Computational Thinking
The five "ways of thinking" that underpin problem solving in computer science. These are tested as both written definitions and applied to novel scenarios.
2.1.1 — Thinking Abstractly
  • Abstraction = removing unnecessary detail to focus on what matters for a given purpose
  • The abstraction is a simplified model — not the real thing
  • Helps manage complexity in large systems
  • Example: a map abstracts away building interiors, keeping only roads and names
  • You must be able to: identify what to include/exclude when creating an abstraction, and explain why abstraction is necessary
  • Devise an abstract model for a scenario (e.g. a library, a traffic system)
2.1.2 — Thinking Ahead
  • Inputs — what data goes in; Outputs — what comes out
  • Preconditions — what must be true before a solution can work (e.g. binary search requires a sorted list)
  • Caching — storing results of expensive computations so they don't need to be repeated. Benefits: faster access. Drawbacks: takes memory, data can go stale
  • Reusable components — functions/modules designed to be used in many programs. Reduces duplication, easier to test and maintain
2.1.3 — Thinking Procedurally
  • Break a problem into components (decomposition)
  • Identify the order of steps needed — sequencing matters
  • Identify sub-procedures: discrete, reusable chunks of a solution
  • Example: "Book a train ticket" → search routes → check availability → enter details → pay → confirm
  • Each sub-procedure can be independently tested and developed
2.1.4 — Thinking Logically
  • Identify decision points in a solution — where the outcome could go different ways
  • Determine the logical conditions that govern each decision (e.g. IF score > 50 THEN pass)
  • Understand how decisions affect flow through a program (branching, loops)
  • Ties closely to Boolean logic: AND, OR, NOT conditions
  • Example: a login system — correct password? → branch to welcome page or error
2.1.5 — Thinking Concurrently
  • Identify parts of a problem that can happen at the same time
  • Concurrent processing — multiple tasks processed in overlapping time periods (not necessarily simultaneously)
  • Benefits: faster overall execution, better use of multi-core processors
  • Trade-offs: race conditions, harder to debug, synchronisation overhead, shared resource conflicts
  • Example: rendering a webpage — fetching HTML, images and CSS can happen concurrently
Abstraction vs Reality — Key Distinction

An abstraction always leaves things out. The question is always: what did we choose to leave out and why?

Good abstraction includes:

  • Data/attributes relevant to the purpose
  • Actions/behaviours that need to be modelled
  • Relationships between components

Good abstraction excludes:

  • Details irrelevant to the problem
  • Physical/real-world noise
  • Anything that would increase complexity without benefit
Exam tip: When asked to "devise an abstract model," always state what you're including AND what you're leaving out, with a brief justification.
Caching — Benefits & Drawbacks
BenefitDrawback
Reduces time to access frequently used dataCache uses memory — expensive
Avoids recomputing expensive resultsCached data can become stale/outdated
Reduces load on slower storage (e.g. disk, network)Cache misses add overhead
Can dramatically speed up repeated operationsManaging cache invalidation is complex
2.2 — Problem Solving & Programming
Programming techniques and computational methods. You need to apply these to unseen scenarios and write/trace pseudocode in the OCR style.
Programming Constructs

Sequence

Instructions execute one after another in order. The default behaviour of any program.

Iteration

Repeating a block of code. FOR loops — known number of repeats. WHILE — condition checked before. DO…UNTIL — condition checked after (always runs at least once).

Branching

IF/ELSEIF/ELSE or SWITCH/CASE statements — directing flow based on a condition.

if score >= 70 then
  print("Distinction")
elseif score >= 50 then
  print("Pass")
else
  print("Fail")
endif
Recursion vs Iteration

Recursion

A function that calls itself. Must have a base case (stopping condition) and a recursive case.

function factorial(n)
  if n == 0 then
    return 1       // base case
  else
    return n * factorial(n-1)
  endif
endfunction
  • Uses the call stack — each call adds a stack frame
  • Elegant for problems with natural recursive structure (trees, factorials)
  • Risk: stack overflow if recursion depth too large

Iteration

Uses loops. Generally more memory-efficient than recursion.

function factorial(n)
  result = 1
  for i = 1 to n
    result = result * i
  next i
  return result
endfunction
  • No risk of stack overflow
  • Often faster (no function call overhead)
  • Can be less readable for recursive-natured problems
RecursionIteration
MemoryHigher (call stack)Lower
SpeedSlower (overhead)Faster
ReadabilityOften cleaner for recursive problemsMore verbose for some
Stack overflow riskYesNo
Best forTrees, divide & conquerSimple repetition
Variables: Local vs Global

Local Variables

  • Declared inside a function/procedure
  • Only accessible within that scope
  • Created when function is called, destroyed when it returns
  • Preferred — prevents unintended side effects

Global Variables

  • Declared outside all functions — accessible everywhere
  • Persist for the lifetime of the program
  • Risky: any function can accidentally modify them
  • Use sparingly — only when truly needed across many functions
Modularity — Functions & Procedures

Function

Returns a value. Called as part of an expression.

function square(x)
  return x * x
endfunction

result = square(5)  // result = 25

Procedure

Does NOT return a value. Performs an action.

procedure printLine(msg)
  print(msg)
  print("--------")
endprocedure

printLine("Hello")

Parameter Passing

By Value — a copy of the value is passed. Changes inside the function do NOT affect the original variable.

By Reference — the memory address is passed. Changes inside the function DO affect the original variable.

Exam tip: Pass by reference is used when you want a procedure to modify a variable. Pass by value is safer — it protects the original data.
Object-Oriented Programming (OOP)

Key Concepts

  • Class — a blueprint/template defining attributes and methods
  • Object — an instance of a class
  • Attribute — a variable belonging to a class (data)
  • Method — a function belonging to a class (behaviour)
  • Constructor — special method called when object is created

Three Pillars

  • Encapsulation — data and methods bundled together; internal data hidden from outside (private attributes)
  • Inheritance — a subclass inherits attributes/methods from a superclass; promotes reuse
  • Polymorphism — different classes can respond to the same method name in different ways (overriding)
// OCR pseudocode style
class Animal
  private name
  public procedure new(animalName)
    name = animalName
  endprocedure
  public function getName()
    return name
  endfunction
  public procedure speak()
    print("...")
  endprocedure
endclass

class Dog inherits Animal
  public procedure speak()     // overrides parent method
    print("Woof!")
  endprocedure
endclass

myDog = new Dog("Rex")
myDog.speak()   // outputs: Woof!
IDE features you should know: breakpoints, stepping through code, variable watch windows, syntax highlighting, auto-complete, error highlighting.
What Makes a Problem Solvable by Computational Methods?
  • The problem can be broken down into logical steps
  • There is a clear, unambiguous solution that can be expressed algorithmically
  • It involves processing data in a well-defined way
  • It would be too large/slow for a human to solve in reasonable time
  • It can be automated and repeated
Not all problems are computable. The Halting Problem (can a program determine if another program will halt?) is famously unsolvable computationally.
Problem Decomposition & Divide and Conquer

Decomposition

Breaking a large problem into smaller, more manageable sub-problems. Each sub-problem can be solved independently and combined.

  • Easier to assign to different developers
  • Each part easier to test in isolation
  • Matches to modular programming

Divide and Conquer

A specific algorithmic strategy: divide the problem into sub-problems of the same type, conquer each recursively, combine results.

  • Examples: merge sort, binary search, quicksort
  • Usually gives O(log n) or O(n log n) complexity
  • Much better than brute-force for large datasets
Computational Methods — The Full List
MethodDescriptionExample
BacktrackingTry a path; if it fails, undo and try another. Systematic trial and error.Maze solving, Sudoku solver
Data MiningSearching large datasets to find patterns, correlations or useful information.Recommender systems, fraud detection
HeuristicsA "good enough" approach when an optimal solution is too costly to compute. Sacrifices accuracy for speed.A* pathfinding, NP-hard approximations
Performance ModellingSimulating a system to predict behaviour before building it — cheaper than real testing.Simulating server load, traffic modelling
PipeliningBreaking a task into stages processed concurrently (each stage works on a different item simultaneously).CPU instruction pipeline, assembly lines
VisualisationRepresenting data graphically to help understand complex relationships and patterns.Network topology diagrams, data dashboards
Heuristics in exams: A heuristic does not guarantee the optimal solution — it trades accuracy for speed. The A* algorithm is a heuristic because it uses an estimated distance (h(n)) that may not be perfectly accurate.
2.3 — Algorithms
The most heavily examined section of Paper 2. Covers complexity analysis, data structure algorithms, all major sorting and searching algorithms, and pathfinding. Expect to write, trace and analyse pseudocode.
Big O Notation — Overview

Big O notation describes the upper bound of an algorithm's time (or space) complexity as the input size n grows. It tells you the worst case — how bad can it get?

The goal: minimise Big O. An algorithm's time complexity is more important to exam boards than space complexity (though you must know both).

The Five Complexities — From Best to Worst
Big ONameDescriptionExample
O(1) Constant Time does not change regardless of input size Accessing an array element by index, push/pop on stack
O(log n) Logarithmic Time grows very slowly — input halved each step Binary search, binary tree operations
O(n) Linear Time grows proportionally with input size Linear search, traversing a linked list
O(n²) Polynomial Time grows as n squared — nested loops Bubble sort, insertion sort, selection sort
O(2ⁿ) Exponential Time doubles for every extra element — extremely slow Brute-force password cracking, recursive Fibonacci (naïve)
Order (best → worst): O(1) → O(log n) → O(n) → O(n log n) → O(n²) → O(2ⁿ)
Note: O(n log n) is where merge sort and quicksort sit — between linear and polynomial.
Recognising Complexity From Code
// O(1) — no loops, independent of n
x = array[0]
print(x)
// O(n) — single loop over n items
for i = 0 to n-1
  print(array[i])
next i
// O(n²) — nested loops
for i = 0 to n-1
  for j = 0 to n-1
    print(array[i][j])
  next j
next i
// O(log n) — halving the problem
// e.g. binary search reduces
// search space by half each step
Key rule: Count the number of nested loops. One loop = O(n). Two nested loops = O(n²). A loop that halves the data each time = O(log n).
Space Complexity

Space complexity = how much extra memory an algorithm needs relative to input size n.

  • Algorithms that work in-place (like bubble sort) use O(1) extra space
  • Merge sort creates copies of sub-arrays → O(n) space
  • Recursive algorithms store stack frames → space complexity depends on recursion depth
  • In exams: the board mainly asks about time complexity; know space complexity exists and what it means
Stack — FILO (First In, Last Out)

Implemented as an array with a single top pointer, initialised at -1 (empty stack).

Operations

// Push — add to top
procedure push(stack, item)
  if top < maxSize-1 then
    top = top + 1
    stack[top] = item
  else
    print("Stack overflow")
  endif
endprocedure

// Pop — remove from top
function pop(stack)
  if top >= 0 then
    item = stack[top]
    top = top - 1
    return item
  else
    print("Stack underflow")
  endif
endfunction

// Peek — see top without removing
function peek(stack)
  return stack[top]
endfunction

// isEmpty
function isEmpty()
  return top == -1
endfunction

Key Points

  • Top pointer starts at -1 (empty)
  • Push: increment top, then insert
  • Pop: read top, then decrement
  • Overflow = push onto full stack
  • Underflow = pop from empty stack
  • Size = top + 1

Uses of Stacks

  • Call stack (function calls in recursion)
  • Undo functionality in editors
  • Browser back button
  • Expression evaluation

Complexity

Push O(1) Pop O(1) Peek O(1)
Queue — FIFO (First In, First Out)

Implemented as an array with front and back pointers. Items join at the back, leave from the front.

// Enqueue — add to back
procedure enqueue(queue, item)
  if back < maxSize-1 then
    queue[back] = item
    back = back + 1
  else
    print("Queue full")
  endif
endprocedure

// Dequeue — remove from front
function dequeue(queue)
  if front != back then
    item = queue[front]
    front = front + 1
    return item
  else
    print("Queue empty")
  endif
endfunction

// isEmpty: front == back
// Size: back - front

Key Points

  • Front and back both start at 0
  • Empty when front == back
  • Size = back − front
  • A circular queue wraps the back pointer round to reuse freed space at the front

Uses of Queues

  • CPU scheduling (process queues)
  • Printer job queues
  • Breadth-first search (uses a queue)
  • Keyboard input buffer
Linked List

A dynamic data structure where each element (node) stores a value and a pointer to the next node. The list ends when a node points to null/None.

Traversal

procedure traverse(head)
  current = head
  while current != null
    print(current.data)
    current = current.next
  endwhile
endprocedure

Advantages over arrays

  • Dynamic size — no need to declare size in advance
  • Efficient insertions/deletions (just update pointers)

Disadvantages

  • No random access — must traverse from head: O(n)
  • Extra memory for pointers
  • Not cache-friendly

Complexity

Insert head O(1) Search O(n) Traversal O(n)
Tree Traversals

A tree is a hierarchical structure with a root node. A binary search tree (BST) has the property: left child < parent < right child.

Depth-First (Post-Order)

OCR spec specifically names post-order for depth-first. Visit left subtree → right subtree → root.

procedure postOrder(node)
  if node != null then
    postOrder(node.left)
    postOrder(node.right)
    print(node.data)
  endif
endprocedure
In-order traversal (left → root → right) of a BST gives nodes in sorted order. This is often tested even though OCR names post-order.

Breadth-First Traversal

Visits nodes level by level (top to bottom, left to right). Uses a queue.

procedure breadthFirst(root)
  queue.enqueue(root)
  while queue NOT empty
    node = queue.dequeue()
    print(node.data)
    if node.left != null
      queue.enqueue(node.left)
    endif
    if node.right != null
      queue.enqueue(node.right)
    endif
  endwhile
endprocedure
Key difference: Depth-first uses a stack (or recursion). Breadth-first uses a queue. This is a common exam question.
Sorting Algorithm Comparison
Bubble Sort
Best caseO(n)
Worst caseO(n²)
SpaceO(1)
Stable?Yes
Insertion Sort
Best caseO(n)
Worst caseO(n²)
SpaceO(1)
Stable?Yes
Merge Sort
Best caseO(n log n)
Worst caseO(n log n)
SpaceO(n)
Stable?Yes
Quick Sort
Best caseO(n log n)
Worst caseO(n²)
SpaceO(log n)
Stable?No
Which to use? Merge sort guaranteed O(n log n) — best for large datasets. Quick sort faster in practice on average but O(n²) worst case. Bubble/Insertion fine for small or nearly-sorted data.
Bubble Sort

Repeatedly compares adjacent pairs and swaps them if out of order. After each pass, the largest unsorted element "bubbles" to its correct position at the end.

function bubbleSort(array)
  n = length(array)
  swapped = True
  while swapped
    swapped = False
    for i = 0 to n-2
      if array[i] > array[i+1] then
        temp = array[i]
        array[i] = array[i+1]
        array[i+1] = temp
        swapped = True
      endif
    next i
  endwhile
  return array
endfunction

Trace: [5, 3, 1, 4]

PassArray after pass
1[3, 1, 4, 5] — 5 settled
2[1, 3, 4, 5] — 4 settled
3[1, 3, 4, 5] — sorted

Key Points

  • The swapped flag is an optimisation — stops early if no swaps in a pass (best case O(n))
  • After pass k, the last k elements are guaranteed sorted
  • Simple but slow — rarely used in practice
Insertion Sort

Builds a sorted portion from left to right. Takes each element and inserts it into its correct position in the already-sorted portion to its left.

function insertionSort(array)
  n = length(array)
  for i = 1 to n-1
    key = array[i]
    j = i - 1
    while j >= 0 AND array[j] > key
      array[j+1] = array[j]
      j = j - 1
    endwhile
    array[j+1] = key
  next i
  return array
endfunction

Trace: [5, 3, 1, 4]

ikeyArray
13[3, 5, 1, 4]
21[1, 3, 5, 4]
34[1, 3, 4, 5]

Key Points

  • The sorted portion grows by one element each iteration
  • Very efficient for nearly sorted data
  • Note: the sorted elements are NOT necessarily the smallest i elements
Merge Sort

Divide and conquer: recursively split the array in half until individual elements, then merge sorted halves back together.

function mergeSort(array)
  if length(array) <= 1 then
    return array
  endif
  mid = length(array) DIV 2
  left = mergeSort(array[0:mid])
  right = mergeSort(array[mid:])
  return merge(left, right)
endfunction

function merge(left, right)
  result = []
  while left != [] AND right != []
    if left[0] <= right[0] then
      result.append(left[0])
      left = left[1:]
    else
      result.append(right[0])
      right = right[1:]
    endif
  endwhile
  return result + left + right
endfunction

Trace: [5, 3, 1, 4]

Split: [5,3] | [1,4]

Split again: [5] [3] [1] [4]

Merge [5][3] → [3,5]

Merge [1][4] → [1,4]

Merge [3,5][1,4] → [1,3,4,5]

Key Points

  • Always O(n log n) — most consistent sort
  • Requires O(n) extra space for the merging step
  • The exact implementation of merge is not required — just how it works conceptually
Quick Sort

Selects a pivot, partitions elements into those less than and greater than the pivot, then recursively sorts each partition.

function quickSort(array)
  if length(array) <= 1 then
    return array
  endif
  pivot = array[mid]
  left = [x for x in array
          if x < pivot]
  middle = [x for x in array
            if x == pivot]
  right = [x for x in array
           if x > pivot]
  return quickSort(left) +
         middle +
         quickSort(right)
endfunction

Trace: [5, 3, 1, 4, 2] — pivot = 3

Pivot = 3. Left = [1,2], Middle = [3], Right = [4,5]

Sort [1,2]: pivot=1 → [1,2] ✓

Sort [4,5]: pivot=4 → [4,5] ✓

Combine: [1,2,3,4,5] ✓

Key Points

  • Pivot choice matters — middle element is common in exams
  • Once a pivot is placed, it never moves
  • Worst case O(n²) when pivot is always smallest/largest
  • O(log n) space (recursive call stack)
Linear Search

Check each element one by one from the start until the target is found or the list is exhausted.

function linearSearch(array, target)
  for i = 0 to length(array)-1
    if array[i] == target then
      return i     // found at index i
    endif
  next i
  return -1        // not found
endfunction

Properties

  • Complexity: O(n)
  • Works on unsorted data
  • Simple to implement
  • Very inefficient for large datasets
  • Best case: target is first element O(1)
  • Worst case: target not in list — checks all n elements
Works unsorted O(n) worst case Simple to code
Binary Search

Divide and conquer. Check the midpoint — if it's the target, done. If target is smaller, search left half; if larger, search right half. Repeat.

function binarySearch(array, target)
  first = 0
  last = length(array) - 1
  while first <= last
    mid = (first + last) DIV 2
    if array[mid] == target then
      return mid
    elseif array[mid] < target then
      first = mid + 1
    else
      last = mid - 1
    endif
  endwhile
  return -1  // not found
endfunction

Trace: find 7 in [1,3,5,7,9,11]

firstlastmidarray[mid]Action
05257 > 5 → first = 3
35497 < 9 → last = 3
3337Found! Return 3

Properties

Sorted data required! O(log n) More complex to code
Precondition: The list MUST be sorted. This is always asked in exams — always state it.
Linear vs Binary Search — When to Use
Linear SearchBinary Search
Data sorted?Not requiredRequired
Time complexityO(n)O(log n)
Best forSmall/unsorted dataLarge sorted data
ImplementationSimpleMore complex
10,000 items worst case~10,000 steps~14 steps
Dijkstra's Shortest Path Algorithm

Finds the shortest path from a source node to all other nodes in a weighted graph. Uses a greedy approach — always processes the unvisited node with the smallest known distance.

Steps:

Assign distance 0 to source, ∞ to all others. Mark all as unvisited.

Select the unvisited node with the smallest distance (start with source).

For each unvisited neighbour, calculate: current node's distance + edge weight. If this is less than the neighbour's current distance, update it.

Mark current node as visited. Visited nodes are never revisited.

Repeat from step 2 until destination is visited (or all nodes visited).

Worked Example

4 2 5 8 3 1 6 A B C D E F

Dijkstra's Table (Source = A, Target = F)

StepVisitedABCDEF
Start—0∞∞∞∞∞
Visit AA042∞∞∞
Visit C (dist 2)A,C042∞5∞
Visit B (dist 4)A,C,B04295∞
Visit E (dist 5)A,C,B,E0429511
Visit D (dist 9)A,C,B,E,D0429510
Visit F (dist 10)All0429510

Shortest path A→F = 10, via A→C→E→F

Weighted graphs only Fails with negative weights Finds shortest path Greedy algorithm
A* Algorithm

A heuristic extension of Dijkstra's. Adds an estimated distance to the destination at each node to guide the search more intelligently.

The Key Formula

f(n) = g(n) + h(n)

g(n) = actual cost from start to node n
h(n) = heuristic estimate from node n to destination
f(n) = total estimated cost of path through n

How it works

Calculate f(n) = g(n) + h(n) for each neighbour

Always expand the node with the lowest f(n)

Stop when destination is reached

vs Dijkstra's

Dijkstra'sA*
Heuristic?NoYes h(n)
Optimal?Yes (always)Yes (if h(n) ≤ true distance)
SpeedSlowerFaster in practice
Use caseAll-pairs shortest pathsSingle target navigation
Why A* is a heuristic: h(n) is an estimate — it might not be exactly right. This makes A* a heuristic algorithm. It's still optimal if h(n) never overestimates (admissible heuristic). Common h(n): straight-line distance to destination.
Exam point: A* is used in GPS navigation and game AI pathfinding. Dijkstra's is better when you need shortest paths to ALL nodes (e.g. network routing). A* is better when you need the path to a specific destination.