← Back to Blake's Learning Hub

Year 11 Catch-Up Session

Filling the remaining 16 gaps from your Year 11 knowledge audit. After this lesson, your Year 11 foundations are complete — then it's full speed on Programming for the Web.

Lesson 14: Year 11 Catch-Up

Data, Structures & Tools — the 16 things that weren't on your radar yet

⏱ 90 minutes 📘 Year 11 — Programming Fundamentals 🎯 SE-11-05, SE-11-06, SE-11-07

1 — Number Systems & Data Representation ~20 min

Why computers think in binary

Every transistor in a processor is either on (1) or off (0). Everything on a screen — text, images, video — is ultimately a long sequence of 1s and 0s. Understanding how numbers are represented lets you understand how data is stored, transmitted, and sometimes corrupted.

The three bases NESA

BaseNameDigits usedWhere you see it
Base 2Binary0, 1CPU registers, file storage, network packets
Base 10Decimal0–9Everywhere in daily life
Base 16Hexadecimal0–9, A–FColour codes, memory addresses, hash outputs

Binary ↔ Decimal NESA

Each binary digit (bit) has a positional value that doubles as you move left: 1, 2, 4, 8, 16, 32, 64, 128…

Bit positions (8-bit / 1 byte): Position: 7 6 5 4 3 2 1 0 Value: 128 64 32 16 8 4 2 1 Example: convert 10110101 to decimal 1×128 + 0×64 + 1×32 + 1×16 + 0×8 + 1×4 + 0×2 + 1×1 = 128 + 32 + 16 + 4 + 1 = 181

🔢 Conversion Practice

0 / 0 correct
Loading…

Hexadecimal NESA

Hex is a shorthand for binary. Every 4 bits maps to exactly one hex digit — so an 8-bit byte becomes two hex characters. This is why memory addresses and colour codes are written in hex.

DecimalBinaryHex
000000
910019
101010A
151111F
2551111 1111FF

💡 Hex in practice

CSS colour #DD6B20 is hex. Break it down: DD = red (221), 6B = green (107), 20 = blue (32). Every colour on screen is stored as 3 bytes of binary data.

Two's Complement — representing negative numbers NESA

Computers can only store 0s and 1s. So how do they store −37? The answer is two's complement.

The process: three steps

  1. Write the positive version in binary. +37 in 8-bit binary = 00100101
  2. Flip every bit (invert). 0010010111011010
  3. Add 1. 11011010 + 00000001 = 11011011

So −37 is stored as 11011011. You can verify: if the leading bit is 1, the number is negative.

Why two's complement?

  • Only one representation of zero (avoids +0 and −0 ambiguity)
  • Normal binary addition works for both positive and negative — no special hardware needed
  • For 8 bits: range is −128 to +127
▶ Challenge: find two's complement of −12

+12 = 00001100 → flip → 11110011 → add 1 → 11110100

⬡ Beyond NESA: why adding two's complement numbers works

+37 = 00100101, −37 = 11011011. Add them: 00100101 + 11011011 = 100000000 — that's a 1 followed by 8 zeros. Drop the overflow bit and you get 00000000 = zero. The math just works, which is why every CPU uses two's complement.

Real and floating point data types NESA

You already know int (whole numbers) and str. Here are the two decimal types:

TypeMeaningExample valuesPython keyword
RealAny number with a decimal point — rational or irrational3.14, −0.5, 2.718…float
Single precision floatReal stored in exactly 32 bits using IEEE 754 formatstored as sign + exponent + mantissaC's float, Python's float is actually 64-bit

⚠️ The float precision trap

Not every decimal can be represented exactly in binary.

Python — float precision
print(0.1 + 0.2)          # You'd expect 0.3
# Actual output: 0.30000000000000004

# Why? 0.1 and 0.2 can't be stored exactly in binary.
# They're stored as the closest representable value.

# Fix: use round() when comparing decimals
print(round(0.1 + 0.2, 1) == 0.3)   # True
⬡ Beyond NESA: IEEE 754 — what's inside a 32-bit float

A single-precision float uses 32 bits split into three parts:

  • 1 bit — sign (0 = positive, 1 = negative)
  • 8 bits — exponent (biased by 127)
  • 23 bits — mantissa (the significant digits)

Value = (−1)^sign × 1.mantissa × 2^(exponent−127). This is why precision problems exist — you're forcing an infinite decimal into 23 bits.

2 — Data Structures: Records, Trees & Files ~25 min

Records NESA

A record represents a single entity with named fields — like one row in a spreadsheet. Unlike an array (which uses integer indices), a record lets you access data by a meaningful name.

Array approach (less readable)

student = ["Blake", 17, "Year 12"]
# Access by position — fragile
name = student[0]

Record approach (named fields)

from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int
    year: str

student = Student("Blake", 17, "Year 12")
print(student.name)  # "Blake"

In Python you can also use a plain dict as an informal record: student = {"name": "Blake", "age": 17}. The dataclass is the more formal, type-safe version.

💡 Record vs Array — the key distinction

Arrays hold multiple values of the same type accessed by index (position). Records hold multiple related fields with different types accessed by name. A record describes one thing; an array describes many of the same thing.

Trees NESA

A tree is a hierarchical data structure — data is organised in a parent-child relationship. Unlike a linked list (one chain), a tree branches.

Vocabulary

TermMeaning
RootThe top node — no parent
Parent / ChildEvery node (except root) has one parent; a node can have multiple children
LeafA node with no children — at the bottom of the tree
SubtreeAny node plus all its descendants
HeightNumber of edges from root to deepest leaf

Binary Search Tree (BST) — a common form NESA

In a BST, every node satisfies one rule: left child < parent < right child. This means searching is fast — at each node you can immediately discard half the tree.

Binary Search Tree with root 4 (blue), inner nodes 2 and 6 (orange), leaves 1, 3, 5, 7 (orange). In-order traversal: 1 2 3 4 5 6 7.

BST containing: 4, 2, 6, 1, 3, 5, 7 — in-order traversal yields sorted output: 1 2 3 4 5 6 7

Search for 5: start at 4 → 5 > 4, go right → 6 → 5 < 6, go left → found. Only 3 comparisons instead of 7.

Implementing a BST node in Python NESA

Python — BST node structure
class Node:
    def __init__(self, value):
        self.value = value
        self.left = None    # smaller values go left
        self.right = None   # larger values go right

class BinarySearchTree:
    def __init__(self):
        self.root = None

    def insert(self, value):
        if self.root is None:
            self.root = Node(value)
        else:
            self._insert_recursive(self.root, value)

    def _insert_recursive(self, node, value):
        if value < node.value:
            if node.left is None:
                node.left = Node(value)
            else:
                self._insert_recursive(node.left, value)
        else:
            if node.right is None:
                node.right = Node(value)
            else:
                self._insert_recursive(node.right, value)

bst = BinarySearchTree()
for v in [4, 2, 6, 1, 3, 5, 7]:
    bst.insert(v)

💡 Trees are everywhere

A file system is a tree. The browser's DOM is a tree — <html> is the root, <body> is a child, each element is a node.

⬡ Beyond NESA: BST vs balanced trees (AVL, Red-Black)

A BST degrades to a linked list if you insert already-sorted data (1, 2, 3, 4… all go right). Balanced trees (AVL, Red-Black) automatically reorganise to keep height minimal — guaranteeing O(log n) search always, not just on average.

Sequential Files NESA

A sequential file stores records one after another in a fixed order. To find a record, you start at the beginning and read forward — you can't jump to record 500 without passing records 1–499 first.

In Python: CSV as the classic example

Python — reading a sequential CSV file
import csv

# Sequential: we must read every row from top to bottom
with open("students.csv", "r") as f:
    reader = csv.reader(f)
    for row in reader:          # row 1, then 2, then 3...
        print(row[0], row[1])   # name, grade

# To find one student, you still read the whole file
# (unless you know the line number — which is unusual)
Sequential FileRandom Access File
Access patternRead from start in orderJump to any record by index
ExampleCSV, log files, audio streamsBinary files with fixed-size records
Best forProcessing all records, append-only logsFrequent lookup by position

Stack & Hash Table implementations NESA

Stack NESA

A stack is LIFO — Last In, First Out. Items are pushed onto the top and popped from the top. The classic analogy is a stack of plates.

Python — Stack (from Lesson 4)
class Stack:
    def __init__(self):
        self._items = []

    def push(self, item):
        self._items.append(item)    # add to top

    def pop(self):
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self._items.pop()    # remove from top

    def peek(self):
        return self._items[-1]      # view top without removing

    def is_empty(self):
        return len(self._items) == 0

Hash Table NESA

A hash table stores key-value pairs and achieves near-constant-time lookup. It works by running the key through a hash function that converts it to an array index.

Hash table: keys James, Ellen, Bill, Susan pass through a hash function (lambda) and are stored at different indices in a storage array containing phone numbers.

Keys (names) → hash function → array index → stored value (phone number). Each key maps to a specific slot.

Python — hash table via dict (no reinventing needed)
# Python's dict IS a hash table — O(1) average lookup
phonebook = {
    "Blake": "0411 000 111",
    "Luis":  "0422 000 222",
    "Jogo":  "0433 000 333",
}

# Lookup: Python hashes "Blake" to find the right slot instantly
print(phonebook["Blake"])   # O(1) — same speed whether 10 or 10,000 entries

# Manual hash table (to understand what dict does under the hood)
class SimpleHashTable:
    def __init__(self, size=10):
        self.table = [None] * size

    def _hash(self, key):
        return sum(ord(c) for c in key) % len(self.table)

    def set(self, key, value):
        index = self._hash(key)
        self.table[index] = (key, value)

    def get(self, key):
        index = self._hash(key)
        entry = self.table[index]
        if entry and entry[0] == key:
            return entry[1]
        return None
⬡ Beyond NESA: hash collisions

Two different keys can produce the same hash index — called a collision. Real hash tables handle this with chaining (each slot holds a linked list of entries) or open addressing (probe the next empty slot). Python's dict uses a sophisticated open-addressing scheme.

3 — Algorithm Modelling Tools ~15 min

Structure charts & refinement diagrams NESA

You've used flowcharts to show how code runs step-by-step. Structure charts show something different: what modules exist and which ones call which.

FlowchartStructure Chart
ShowsControl flow (sequence, selection, iteration)Module hierarchy (which calls which)
ShapesDiamonds (decisions), rectangles (steps), arrowsRectangles (modules), lines with arrows showing calls
Best forExplaining a single algorithm's logicDesigning the structure of a whole program

Structure chart example

Structure chart for a login system: main_program calls get_input, validate_user, show_result. validate_user calls check_password and check_account_active. Data (username, password) flows down; True/False returns up.

Structure chart — login system. Arrows show which module calls which, not control flow.

Refinement diagrams NESA

A refinement diagram is how you design top-down — start with a vague high-level step, then break it down. Each box gets "refined" into smaller boxes until every box is simple enough to code directly.

Refinement diagram: Level 1 has validate_user. Level 2 breaks it into check_password and check_account_active. Level 3 breaks those into hash_input, compare_to_stored_hash, and query_db_for_status.

Stepwise refinement — each level decomposes the level above until every box is one simple function.

💡 The key insight

Structure charts are drawn before you write code — they're a design tool. Once you have a structure chart, each box becomes a function or method. The connections tell you what parameters to pass and what to return.

Subroutine connections — data flow in algorithms NESA

When one algorithm calls another, there are two things flowing between them:

  • Parameters — data passed into the called subroutine (going down in the structure chart)
  • Return values — data passed back to the caller (going up)
Python — subroutine connections with clear data flow
def calculate_total(items: list) -> float:
    """Called by: checkout_process. Receives: list of prices. Returns: total."""
    subtotal = sum(items)
    return apply_discount(subtotal)     # calls another subroutine

def apply_discount(amount: float) -> float:
    """Called by: calculate_total. Receives: subtotal. Returns: discounted price."""
    if amount > 100:
        return amount * 0.9             # 10% discount
    return amount

def checkout_process(cart: list) -> None:
    """Top-level. Calls: calculate_total."""
    total = calculate_total(cart)       # data flows down (cart → items), up (total ← return)
    print(f"Total: ${total:.2f}")

🗂️ Challenge: draw the structure chart

For the code above, draw a structure chart showing the three modules. Indicate what data passes down (as parameters) and what comes back up (as return values) on each connecting line.

▶ Show answer

checkout_process → calls calculate_total (passes: cart, receives: total) → calls apply_discount (passes: subtotal, receives: discounted amount)

4 — Standard Modules, Testing & IDE Debugging ~20 min

Standard modules NESA

A standard module is pre-written, pre-tested code that ships with Python — no installation needed, just import. They exist so you never have to reinvent the wheel for common tasks.

ModuleWhat it doesExample use
mathMathematical functionsmath.sqrt(144), math.pi, math.floor(3.7)
randomRandom number generationrandom.randint(1, 6) (dice roll), random.shuffle(deck)
datetimeDates and timesdatetime.date.today(), datetime.timedelta(days=7)
osFile system and OS interactionos.path.exists("file.txt"), os.getcwd()
csvRead/write CSV filescsv.reader(f), csv.writer(f)
jsonEncode/decode JSONjson.dumps(data), json.loads(text)
Python — using three standard modules
import math
import random
import datetime

# math
radius = 5
area = math.pi * radius ** 2
print(f"Circle area: {area:.2f}")      # 78.54

# random
dice = random.randint(1, 6)
print(f"You rolled: {dice}")

# datetime
today = datetime.date.today()
due_date = today + datetime.timedelta(days=14)
print(f"Assignment due: {due_date}")

🌋 Volcanic Pantheon connection

Your my_api.py file in BlakeLovesJogo is essentially a hand-written standard module — greet(), shout(), add(), multiply(). The only difference from Python's math module is that math was written by the Python core team and ships everywhere. The concept is identical: import it, call its functions, done.

Path coverage testing NESA

You already know boundary value testing (test at edges: 0, max, one below max). Path coverage is a different idea: every possible branch through the code must be executed by at least one test case.

Python — how many paths does this function have?
def classify_score(score: int) -> str:
    if score >= 90:          # branch 1: True / False
        return "HD"
    elif score >= 75:        # branch 2: True / False
        return "Distinction"
    elif score >= 50:        # branch 3: True / False
        return "Pass"
    else:
        return "Fail"

❌ Insufficient — boundary only

Test: score = 90 (edge of HD)

Test: score = 0 (minimum)

Misses the Distinction and Pass branches entirely.

✅ Path coverage — all branches hit

Test 1: score = 95 → "HD" (branch 1 True)

Test 2: score = 80 → "Distinction" (branch 2 True)

Test 3: score = 60 → "Pass" (branch 3 True)

Test 4: score = 40 → "Fail" (else branch)

💡 Boundary vs Path coverage — when to use each

Boundary testing checks that edge values don't crash or give wrong results. Path coverage ensures every branch of your logic has been exercised at least once. A good test suite does both.

▶ Challenge: how many test cases does path coverage require for this function?
def check_login(username, password, is_admin):
    if username == "":
        return "error: no username"
    if password == "":
        return "error: no password"
    if is_admin:
        return "admin dashboard"
    return "user dashboard"

Answer: 4 test cases — empty username, empty password, is_admin=True (with valid credentials), is_admin=False (with valid credentials). Each covers a distinct path through the function.

Debugging interfaces between functions NESA

Most bugs don't live inside a function — they live at the boundary between functions. The caller passes the wrong type, or the wrong shape of data, or the callee returns something the caller doesn't expect.

Python — debugging the interface, not the internals
def get_student_average(scores: list) -> float:
    return sum(scores) / len(scores)

def grade_student(student_name: str, scores) -> str:
    avg = get_student_average(scores)
    if avg >= 50:
        return f"{student_name}: Pass"
    return f"{student_name}: Fail"

# Bug: scores was accidentally passed as a string, not a list
result = grade_student("Blake", "85 90 78")
# get_student_average receives "85 90 78" (a string, not a list)
# sum("85 90 78") → TypeError  ← the error is at the interface

# How to debug interfaces: check what you're sending IN and getting back OUT
print(type(scores))          # see what's actually arriving
print(repr(scores))          # see the exact value
# Or use a debugger breakpoint right at the function call

When a function produces unexpected results, check what is being passed in first — not just what happens inside it.

IDE debugger NESA

Most IDEs include a built-in debugger that is faster and cleaner than inserting print() statements — and leaves no debug output in finished code.

The three tools you need to know

ToolWhat it doesBetter than print because…
BreakpointPauses execution at a line you choose — click the gutter (line numbers area) in VSCodeNo code to add/remove; pause exactly where you want
Step Over (F10)Execute one line, stay at the same level — don't descend into function callsSee line-by-line what happens without print everywhere
Step Into (F11)Execute one line and descend into the function call — follow the code into the subroutineInspect what happens inside a function call without modifying it

Watches

In the Variables and Watch panel, type any expression and see its value update in real time as you step through code — every variable simultaneously, without writing a single extra line.

💡 How to set a breakpoint in VSCode

1. Open your Python file → hover over the grey area left of a line number → a red dot appears → click it to set the breakpoint.

2. Press F5 (Run and Debug) instead of the normal Run button.

3. Execution pauses at your breakpoint — all variable values are visible in the left panel. Use F10 to step line by line.

✅ Year 11 foundations: complete

This lesson covers the remaining Year 11 Programming Fundamentals topics:

Next up → Lesson 15: Client-Server Architecture & APIs