OCR A-Level CS: Component 02 - Algorithms & Programming

H446/02     2 hours 30 minutes     40% of A-Level     Topics 2.1 – 2.3

2.1 - Elements of computational thinking

2.1.1–2.1.5  The five aspects of computational thinking

Thinking abstractly (2.1.1)
Removing unnecessary detail to focus on what matters for the problem. An abstraction is a simplified model of reality.
Why needed: complex real-world problems are too detailed to solve directly, so abstraction makes them manageable.
Examples: a map is an abstraction of the real world; a class is an abstraction of a real-world object.
Thinking ahead (2.1.2)
Identifying inputs and outputs before solving. Defining preconditions.
Caching: storing results of expensive operations for reuse. Benefit: faster. Drawback: uses memory, stale data.
Reusable components: write code once, use many times. Reduces duplication, easier to maintain.
Thinking procedurally (2.1.3)
Breaking a problem into a series of ordered steps. Identifying sub-procedures. Determining the sequence of operations needed.
Closely related to decomposition: identifying which components are needed and in what order.
Thinking logically (2.1.4)
Identifying decision points in a solution. Determining the conditions that affect outcomes. Tracing how decisions affect program flow.
Examples: if/else conditions, loops with conditions, case statements.
Thinking concurrently (2.1.5)
Identifying which parts of a problem can be tackled at the same time.
Benefits: faster execution for tasks that can be parallelised.
Trade-offs: not all problems can be parallelised (some parts are sequential); synchronisation overhead; race conditions.

Exam questions on computational thinking often ask you to apply one of these five aspects to a given scenario, e.g. "explain how abstraction was used in designing this system."

2.2 - Problem solving and programming

2.2.1  Programming techniques

Programming constructs: sequence (instructions in order), iteration (loops: for, while, do-while), branching (if/else, case/switch).


Recursion: a function that calls itself. Needs a base case (termination condition) to prevent infinite recursion. Call stack grows with each recursive call.

Recursion advantages
Elegant, concise code for naturally recursive problems (trees, fractals, factorial). Easier to read for some problems.
Recursion disadvantages
Higher memory use (stack frames). Stack overflow risk. Often slower than iterative equivalent due to function call overhead.

Variables:

Global variables
Accessible throughout entire program. Convenient but can cause side effects, as any function can change them unexpectedly.
Local variables
Only accessible within the function/block they're declared in. Better encapsulation. Destroyed when function returns.

Modularity: breaking a program into separate functions/procedures. Benefits: easier to test, debug, maintain and reuse. Enables parallel development by a team.

Pass by value
A copy of the variable is passed. Changes inside the function don't affect the original. Safer.
Pass by reference
The memory address is passed. Changes inside the function DO affect the original. More efficient for large data.

2.2.1  Use of an IDE (development tools)

An Integrated Development Environment (IDE) brings the tools needed to write, test and debug a program into one application. The spec expects you to know the main features and how each one helps the developer.

Editor features
Syntax highlighting (colours keywords), auto-indentation and prettyprinting (consistent layout), auto-complete, and line numbering. These speed up writing code and cut down syntax errors.
Error diagnostics & debugging
Breakpoints pause execution at a chosen line; single stepping runs one line at a time; a variable watch shows values as they change. Together these help locate logic errors.
Run-time environment
Lets the program be executed and tested inside the IDE, with errors reported, so code does not need to be set up separately to run.
Built-in translator(s)
An interpreter allows quick line-by-line testing during development; a compiler produces the final executable. Both report errors to the programmer.

A common question is "describe two features of an IDE and how they help a programmer." Pair each feature with its benefit, e.g. "breakpoints let the programmer pause the program to inspect variable values and find where a logic error occurs."

2.2.2  Computational methods

Before choosing a technique you must work out whether a problem can be solved computationally at all, and break it down so that it can.

Solvable by computational methods?
A problem suits a computational approach if it can be expressed as clear steps (an algorithm) with definable inputs and outputs. Some problems are non-computable or impractical to solve in a reasonable time.
Problem recognition
Identifying what the actual problem is, from a scenario or set of requirements, before any solution is designed.
Problem decomposition
Breaking a problem into smaller, more manageable sub-problems that can each be solved and tested on their own.
Use of abstraction
Modelling the problem with only the essential detail, removing anything irrelevant, so it is simpler to turn into a computable solution.

Once the problem is understood, these computational methods help solve it:

Divide and conquer
Break problem into smaller sub-problems of the same type. Solve recursively. Combine results. Examples: merge sort, binary search, quick sort.
Backtracking
Explore all possible solutions. When a dead end is reached, backtrack and try another path. Examples: maze solving, Sudoku, N-queens problem.
Data mining
Discovering patterns in large datasets. Used for: fraud detection, market analysis, recommendation systems.
Heuristics
Rules of thumb that find good-enough solutions quickly when an optimal solution is impractical. Used in A* pathfinding, NP-hard problems.
Performance modelling
Simulating system behaviour to predict performance before building. Used for capacity planning, bottleneck identification.
Pipelining
Breaking a task into stages that can be processed concurrently on different data. Used in CPUs, compilers, data processing.
Visualisation
Representing data graphically to aid understanding. Helps identify patterns, trends and outliers in complex data.

2.3 - Algorithms

2.3.1a  Algorithm complexity: Big O notation

Big O notation describes how execution time or space grows as input size n increases. We care about the worst case and the dominant term.

NotationNameExampleNotes
O(1)ConstantArray access by index, hash table lookupAlways same time regardless of n
O(log n)LogarithmicBinary search, BST operations (balanced)Very efficient: doubling the input adds only one extra step
O(n)LinearLinear search, traversing a linked listProportional to input size
O(n log n)LinearithmicMerge sort, quick sort (average)Efficient for sorting
O(n²)Polynomial/QuadraticBubble sort, insertion sortNested loops; poor for large n
O(2ⁿ)ExponentialBrute force subset problems, recursive FibonacciImpractical for large n

To determine Big O: identify the dominant loop structure. One loop over n = O(n). Nested loop = O(n²). Halving the problem each step = O(log n). Drop constants and lower-order terms.

2.3.1b  Sorting algorithms

Bubble sort - O(n²)
Repeatedly compare adjacent pairs, swap if out of order. Each pass bubbles the largest unsorted element to its correct position.
Best case: O(n) if already sorted (with optimisation). Simple but inefficient for large datasets.
Insertion sort - O(n²)
Build sorted portion one element at a time. Take next element, insert it into correct position in already-sorted portion by shifting larger elements right.
Efficient for nearly-sorted data. Good for small datasets. Stable.
Merge sort - O(n log n)
Divide and conquer. Split list in half recursively until single elements. Merge pairs of sorted sublists. Always O(n log n), giving consistent performance.
Requires O(n) extra space. Stable sort.
Quick sort - O(n log n) avg, O(n²) worst
Choose pivot. Partition: elements < pivot left, elements > pivot right. Recursively sort each partition.
Worst case O(n²) if pivot consistently worst choice (sorted array with first element pivot). In-place. Not stable. Very fast in practice.

Merge sort = guaranteed O(n log n) but needs extra memory. Quick sort = faster in practice but O(n²) worst case. Bubble/insertion sort fine for small or nearly-sorted data only.

2.3.1c  Search algorithms

Linear search - O(n)
Check each element one by one until found or end reached. Works on unsorted data. Simple. Inefficient for large datasets.
Binary search - O(log n)
Requires sorted data. Compare target with middle element. If equal: found. If less: search left half. If greater: search right half. Repeat.
Highly efficient, as it eliminates half the data each step.

2.3.1d  Graph/tree traversal and pathfinding

Depth-first traversal (post-order)
Uses a stack. Explores as far as possible down each branch before backtracking. Post-order: Left → Right → Root. Used for: expression evaluation, topological sort, detecting cycles.
Breadth-first traversal
Uses a queue. Explores all nodes at current depth before moving deeper. Finds shortest path in unweighted graphs. Used for: shortest path, level-order traversal.
Dijkstra's shortest path
Finds shortest path from a source node to all other nodes in a weighted graph with non-negative weights. Uses a priority queue, always processing the lowest-cost unvisited node first. Greedy algorithm.
A* algorithm
Extension of Dijkstra's. Uses a heuristic (estimated distance to goal) to guide search. f(n) = g(n) + h(n) where g = cost so far, h = heuristic estimate. Faster than Dijkstra for single-target pathfinding. Used in games and GPS navigation.

Dijkstra's vs A*: Dijkstra finds shortest path to ALL nodes. A* finds shortest path to ONE target node faster by using a heuristic to prioritise promising paths.

2.3.1e  Algorithms for the main data structures

The data structures themselves are covered in Paper 1 (1.4.2). The spec also expects the algorithms to add to, remove from and traverse them, shown below in the OCR reference language. Tree traversal (depth-first and breadth-first) is in the previous section.

Stack (LIFO) using an array and a top pointer. Operations: push, pop, peek, isEmpty.

// push adds to the top; pop removes from the top procedure push(item) if top == maxSize - 1 then print("Stack overflow") // stack is full else top = top + 1 stack[top] = item endif endprocedure function pop() if top == -1 then // isEmpty return "Stack underflow" else item = stack[top] top = top - 1 // logically removes the item return item endif endfunction

Queue (FIFO) as a circular queue with front and rear pointers and a count. Operations: enqueue, dequeue, peek, isEmpty.

// enqueue adds at the rear; dequeue removes from the front procedure enqueue(item) if count == maxSize then print("Queue is full") else queue[rear] = item rear = (rear + 1) MOD maxSize // wrap around the array count = count + 1 endif endprocedure function dequeue() if count == 0 then // isEmpty return "Queue is empty" else item = queue[front] front = (front + 1) MOD maxSize count = count - 1 return item endif endfunction

Linked list: each node holds data plus a pointer to the next node. Traverse by following the pointers from the head until the null pointer is reached.

// follow the pointers from the head, printing each node procedure traverse() current = head while current != null print(current.data) current = current.next endwhile endprocedure

Add to a linked list by creating a node and pointing the previous node's pointer at it; remove by making the previous node point past the deleted node to the one after. Big O: stack and queue push/pop are O(1); linked list search is O(n) because there is no random access.

2.2.1 - Object-oriented programming

Code language

The exam is always marked in OCR's Exam Reference Language. Switch to C# to see the same examples written in a real language. The theory (and the quiz) stays the same in both.

2.2.1 a  Core OOP vocabulary you must be able to define

Object-oriented programming (OOP) models a problem as a set of objects that hold their own data and the operations that act on it. The exam tests both the theory (defining these terms) and the practice (reading and writing class code in the OCR reference language).

Class
Blueprint/template defining attributes and methods for a type of object.
Object / instance
Instance of a class. Has its own attribute values.
Attribute (field / property)
A variable that belongs to an object and stores its state, e.g. name, age. Declared inside the class.
Method
A subroutine that belongs to a class. A function method returns a value; a procedure method does not.
Instantiation
The act of creating an object from a class using the new keyword, which runs the constructor.
Constructor
The special method (named new in the OCR language) that runs automatically when an object is instantiated, used to set up its attributes.
Encapsulation
Bundling attributes and methods together. Hides internal state, which is only accessible via methods (getters/setters).
Inheritance
A subclass inherits attributes and methods of a parent class. Enables code reuse. "is-a" relationship.
Polymorphism
Same method name behaves differently in different classes. Overriding parent methods in subclasses.
Getter / setter
A getter (accessor) returns a private attribute; a setter (mutator) changes it, usually after validating the new value.

Learn the one-line definitions of class, object, attribute, method, encapsulation, inheritance and polymorphism word for word. These are common 1 to 2 mark recall questions, and the mark scheme wants the keyword (e.g. "blueprint", "instance", "overrides").

2.2.1 b  Defining a class: attributes, the constructor and adding methods

In the OCR Exam Reference Language a class is written between class and endclass. Inside it you declare attributes (each marked public or private) and methods (each a procedure or a function). The constructor is always a procedure called new.

class Pet private name private age // Constructor: runs when the object is created public procedure new(givenName, givenAge) name = givenName age = givenAge endprocedure // A function method returns a value public function getName() return name endfunction // A procedure method performs an action, returns nothing public procedure birthday() age = age + 1 endprocedure endclass
class Pet { private string name; private int age; // Constructor: same name as the class, runs when the object is created public Pet(string givenName, int givenAge) { name = givenName; age = givenAge; } // A method that returns a value public string GetName() { return name; } // A void method performs an action, returns nothing public void Birthday() { age = age + 1; } }

Adding a new method means writing another procedure/endprocedure or function/endfunction block inside the class, before endclass. Choose function if it should hand a value back (use return), and procedure if it just does something.

class ... endclass
Opens and closes the class definition.
public / private
Access modifier placed before every attribute and method. private = usable only inside the class.
procedure new(...)
The constructor. Parameters carry in the starting values for the attributes.
function ... return
A method that returns a value to whatever called it.

Marks are lost if the constructor is not named new, or if attributes are not given an access modifier. Every method needs its matching endprocedure or endfunction.

2.2.1 c  Writing statements to instantiate objects and call methods

To instantiate an object you assign new ClassName(arguments) to a variable. The arguments are passed to the constructor. You then use dot notation, object.method(), to run its methods.

// Instantiation statement: create a Pet object called myPet myPet = new Pet("Rex", 3) // Create a second, independent object from the same class yourPet = new Pet("Milo", 5) // Call methods on an object using dot notation print(myPet.getName()) // outputs Rex myPet.birthday() // age is now 4 print(myPet.getName()) // still Rex; only myPet changed, not yourPet
// Instantiation statement: create a Pet object called myPet Pet myPet = new Pet("Rex", 3); // Create a second, independent object from the same class Pet yourPet = new Pet("Milo", 5); // Call methods on an object using dot notation Console.WriteLine(myPet.GetName()); // outputs Rex myPet.Birthday(); // age is now 4 Console.WriteLine(myPet.GetName()); // still Rex; only myPet changed, not yourPet

Each new creates a separate object with its own copy of the attributes, so changing myPet has no effect on yourPet. You can also store objects in an array and loop through them:

pets = [new Pet("Rex", 3), new Pet("Milo", 5)] for each p in pets p.birthday() next p
Pet[] pets = { new Pet("Rex", 3), new Pet("Milo", 5) }; foreach (Pet p in pets) { p.Birthday(); }

Two things examiners look for in an instantiation answer: the new keyword, and arguments that match the constructor's parameters in order. Calling a method needs the object name, a dot, then the method name with brackets, e.g. myPet.getName().

2.2.1 d  Encapsulation: private attributes with getters and setters

Encapsulation means making attributes private so outside code cannot read or change them directly, then providing getter and setter methods as the only way in. This lets the class validate changes and protects its data from being put into an invalid state.

class Pet private name private age public procedure new(givenName, givenAge) name = givenName age = givenAge endprocedure // Getter (accessor): returns the private attribute public function getAge() return age endfunction // Setter (mutator): validates, then changes the attribute public procedure setAge(newAge) if newAge >= 0 then age = newAge endif endprocedure endclass
class Pet { private string name; private int age; public Pet(string givenName, int givenAge) { name = givenName; age = givenAge; } // Getter (accessor): returns the private attribute public int GetAge() { return age; } // Setter (mutator): validates, then changes the attribute public void SetAge(int newAge) { if (newAge >= 0) { age = newAge; } } }

Outside the class you must go through these methods. myPet.setAge(4) is allowed; trying to reach the data directly with myPet.age = 4 would fail because age is private.

Why make attributes private?
So they can only be changed through validated setters. This prevents invalid data (e.g. a negative age) and stops other parts of the program causing unexpected side effects.
Benefit of getters / setters
The internal data representation can change without breaking code that uses the class, because access is controlled through a fixed interface of methods.

If asked why encapsulation is "good practice", say: it protects data integrity through validation, hides internal detail (abstraction), and makes the program easier to maintain because each class controls its own data.

2.2.1 e  Inheritance: reusing a class with inherits and super

Inheritance lets a new class take on the attributes and methods of an existing one. Write class Child inherits Parent. Inside the child's constructor, super.new(...) calls the parent's constructor so inherited attributes are set up correctly, then the child adds its own.

class Dog inherits Pet private breed public procedure new(givenName, givenAge, givenBreed) super.new(givenName, givenAge) // run Pet's constructor first breed = givenBreed // then set the new attribute endprocedure public function getBreed() return breed endfunction endclass
class Dog : Pet // ': Pet' means Dog inherits Pet { private string breed; public Dog(string givenName, int givenAge, string givenBreed) : base(givenName, givenAge) // run Pet's constructor first { breed = givenBreed; // then set the new attribute } public string GetBreed() { return breed; } }

A Dog object now has everything a Pet has plus its own breed:

myDog = new Dog("Rex", 3, "Labrador") print(myDog.getName()) // getName() is inherited from Pet print(myDog.getBreed()) // getBreed() is defined in Dog myDog.birthday() // birthday() is inherited from Pet
Dog myDog = new Dog("Rex", 3, "Labrador"); Console.WriteLine(myDog.GetName()); // GetName() is inherited from Pet Console.WriteLine(myDog.GetBreed()); // GetBreed() is defined in Dog myDog.Birthday(); // Birthday() is inherited from Pet

Inheritance models an "is-a" relationship (a Dog is a Pet). Do not confuse it with composition ("has-a"), where an object holds other objects as attributes, e.g. a Car has an Engine. The keyword is inherits, and super reaches the parent class.

2.2.1 f  Polymorphism and method overriding

Polymorphism means "many forms". A subclass can override an inherited method by defining its own version with the same name. The same method call then produces different behaviour depending on which class the object belongs to.

class Pet public function speak() return "Some generic sound" endfunction endclass class Dog inherits Pet public function speak() // overrides Pet.speak() return "Woof" endfunction endclass class Cat inherits Pet public function speak() // overrides Pet.speak() return "Meow" endfunction endclass
class Pet { // 'virtual' lets a subclass override this method public virtual string Speak() { return "Some generic sound"; } } class Dog : Pet { public override string Speak() // overrides Pet.Speak() { return "Woof"; } } class Cat : Pet { public override string Speak() // overrides Pet.Speak() { return "Meow"; } }
animals = [new Dog(), new Cat(), new Pet()] for each a in animals print(a.speak()) // outputs Woof, then Meow, then Some generic sound next a
Pet[] animals = { new Dog(), new Cat(), new Pet() }; foreach (Pet a in animals) { Console.WriteLine(a.Speak()); // outputs Woof, then Meow, then Some generic sound }

The same line a.speak() runs different code each time because the object decides which version applies. That is polymorphism in action.

Overriding (a subclass redefining an inherited method with the same name) is the form of polymorphism the exam expects. The benefit: you can treat different object types uniformly through one shared method name, which keeps code flexible and extensible.

2.2.1 g  Advantages of OOP (and when it suits a problem)

Reusability
Inheritance lets you reuse a tested superclass in many subclasses instead of rewriting code, supporting the "write once, use many times" principle.
Maintainability
Encapsulation keeps each class self-contained, so a change inside one class is unlikely to break others. Bugs are easier to isolate.
Models the real world
Objects map naturally onto real things (a Customer, an Account), making large systems easier to design and understand.
Reduced duplication
Shared behaviour lives in one superclass. Polymorphism lets one method name cover many object types.
Easier teamwork
Different programmers can work on different classes at the same time through their public interfaces.
Extensibility
New subclasses can be added without changing existing, working code.

A balanced answer also notes drawbacks: OOP adds complexity and can be overkill for small, simple programs, and there can be a slight performance overhead. For tiny scripts a procedural approach may be quicker to write.

2.2.1 h  OOP syntax cheat sheet

You want to...OCR reference language
Define a classclass Pet ... endclass
Declare a private attributeprivate name
Declare a public attributepublic score
Write the constructorpublic procedure new(...) ... endprocedure
Add a value-returning methodpublic function getName() ... return name ... endfunction
Add an action methodpublic procedure birthday() ... endprocedure
Instantiate an objectmyPet = new Pet("Rex", 3)
Call a methodmyPet.getName()
Inherit from a classclass Dog inherits Pet
Call the parent constructorsuper.new(...)
You want to...C#
Define a classclass Pet { ... }
Declare a private attributeprivate string name;
Declare a public attributepublic int score;
Write the constructorpublic Pet(...) { ... } (named after the class)
Add a value-returning methodpublic string GetName() { return name; }
Add an action methodpublic void Birthday() { ... }
Instantiate an objectPet myPet = new Pet("Rex", 3);
Call a methodmyPet.GetName();
Inherit from a classclass Dog : Pet
Call the parent constructor: base(...) after the constructor

OCR's reference language has no separate "override" keyword: you override simply by redefining a method of the same name in the subclass. Constructors are always called new, and there is no destructor.

2.2.1 i  Test yourself: interactive OOP quiz

Ten questions covering vocabulary, syntax and code reading. Pick an answer to see why it is right or wrong, then move on.