Principles of Programming

AS Level — Unit 1: Fundamentals of Computer Science

Programming Paradigms

A programming paradigm is a fundamental style or approach to programming that defines how we structure and organise code to solve problems.

There are several major paradigms, and many modern languages support more than one (these are called multi-paradigm languages). The WJEC AS specification requires knowledge of four paradigms: procedural, event-driven, visual and mark-up languages.

Procedural Programming

Procedural programming organises code as a sequence of instructions (statements) that are executed in order. The program is broken down into procedures (also called subroutines, functions, or methods) that perform specific tasks.

Key features:

  • Programs follow a top-down design – the problem is broken into smaller sub-problems, each solved by a procedure.
  • Data and procedures are separate – data is passed to procedures via parameters or accessed through global variables.
  • Uses sequence, selection (if/else), and iteration (loops) as control structures.
  • Variables have defined scope (local or global).
# Procedural example: calculating average of a list
def calculate_total(numbers):
    total = 0
    for num in numbers:
        total += num
    return total

def calculate_average(numbers):
    total = calculate_total(numbers)
    return total / len(numbers)

marks = [72, 85, 64, 91, 78]
avg = calculate_average(marks)
print(f"Average: {avg}")
' Procedural example: calculating average of a list
Function CalculateTotal(numbers() As Double) As Double
    Dim total As Double = 0
    For Each num As Double In numbers
        total += num
    Next
    Return total
End Function

Function CalculateAverage(numbers() As Double) As Double
    Dim total As Double = CalculateTotal(numbers)
    Return total / numbers.Length
End Function

Dim marks() As Double = {72, 85, 64, 91, 78}
Dim avg As Double = CalculateAverage(marks)
Console.WriteLine("Average: " & avg)

Examples of procedural languages: C, Pascal, BASIC, COBOL.

Event-Driven Programming

In event-driven programming, the flow of the program is determined by events — user actions or system-generated signals — rather than a fixed sequential order. The program waits for events to occur and responds to them by running the appropriate event handler (a subroutine triggered by a specific event).

Key features:

  • The program runs an event loop that continuously listens for events.
  • Code is organised into event handlers attached to specific controls or triggers.
  • The order of execution depends on what the user does, not on the programmer’s predetermined sequence.
  • Well suited to graphical user interfaces (GUIs) where user interaction drives the program.
# Event-driven example using tkinter (Python GUI library)
import tkinter as tk

def on_button_click():
    label.config(text="Button was clicked!")

def on_key_press(event):
    label.config(text=f"Key pressed: {event.char}")

window = tk.Tk()
window.title("Event-Driven Example")

button = tk.Button(window, text="Click Me", command=on_button_click)
button.pack()

label = tk.Label(window, text="Waiting for event...")
label.pack()

window.bind("<Key>", on_key_press)
window.mainloop()  # Event loop — waits for events

Examples: Visual Basic .NET (Windows Forms), JavaScript (web browsers), Python tkinter.

Visual Programming

Visual programming environments allow programmers to create programs by manipulating graphical elements (blocks, icons, flowchart symbols) rather than writing text-based code. The visual representation directly reflects the program’s structure.

Key features:

  • Programs are built by dragging and connecting visual blocks or components.
  • Reduces the risk of syntax errors since the environment enforces valid connections.
  • Makes programming more accessible to beginners and non-programmers.
  • Particularly useful for education, simple automation, and rapid prototyping.

Examples: Scratch (block-based, education), MIT App Inventor (mobile apps), LabVIEW (scientific instrumentation), Blockly (Google’s visual programming library).

Mark-Up Languages

A mark-up language uses tags embedded in text to define the structure, formatting, or meaning of content. It is not a programming language in the traditional sense — it does not contain logic, loops, or variables — but it instructs how content should be displayed or structured.

Key features:

  • Tags surround content to describe what it is (e.g. <h1> for a top-level heading).
  • The tags themselves are not displayed — they are interpreted by a browser or processor.
  • Separates content from presentation.
  • Used for web pages, document formatting, and data exchange.
<!-- HTML — HyperText Markup Language -->
<!DOCTYPE html>
<html>
  <head>
    <title>My Page</title>
  </head>
  <body>
    <h1>Welcome</h1>
    <p>This is a <strong>paragraph</strong> with bold text.</p>
    <ul>
      <li>Item one</li>
      <li>Item two</li>
    </ul>
  </body>
</html>

Examples: HTML (web pages), XML (data exchange), Markdown (documentation), CSS (styling — technically a style sheet language but works with mark-up).

Paradigm Comparison Table

Feature Procedural Event-Driven Visual Mark-Up
Core idea Sequence of instructions Respond to events Graphical blocks Tags describe content
Control flow Top-down, sequential Determined by events Visual connections N/A (not executable logic)
Typical use General computation GUIs, user interaction Education, rapid dev Web pages, data
Examples C, Pascal, BASIC VB.NET, JavaScript Scratch, App Inventor HTML, XML

For the exam, be able to describe each paradigm, identify examples of each, and explain the differences between them. A key distinction: procedural programs follow a fixed sequence; event-driven programs respond to unpredictable user actions. Visual languages use graphical blocks instead of text. Mark-up languages describe structure, not logic.


Object-Oriented Programming Concepts in Detail

Classes and Objects

A class is a blueprint or template that defines the attributes (data) and methods (behaviour) that objects of that type will have. An object is a specific instance of a class, with its own set of attribute values.

Think of a class as a cookie cutter and objects as the individual cookies made from it. The class defines the shape; each object is a distinct cookie.

class Dog:
    def __init__(self, name, breed, age):
        self.name = name        # Attribute
        self.breed = breed      # Attribute
        self.age = age          # Attribute

    def bark(self):             # Method
        return f"{self.name} says Woof!"

    def get_info(self):         # Method
        return f"{self.name} is a {self.age}-year-old {self.breed}"

# Creating objects (instances)
dog1 = Dog("Rex", "German Shepherd", 5)
dog2 = Dog("Bella", "Labrador", 3)

print(dog1.bark())       # Output: Rex says Woof!
print(dog2.get_info())   # Output: Bella is a 3-year-old Labrador
Class Dog
    Public Property Name As String
    Public Property Breed As String
    Public Property Age As Integer

    Sub New(name As String, breed As String, age As Integer)
        Me.Name = name
        Me.Breed = breed
        Me.Age = age
    End Sub

    Function Bark() As String
        Return Name & " says Woof!"
    End Function

    Function GetInfo() As String
        Return Name & " is a " & Age & "-year-old " & Breed
    End Function
End Class

Dim dog1 As New Dog("Rex", "German Shepherd", 5)
Dim dog2 As New Dog("Bella", "Labrador", 3)

Console.WriteLine(dog1.Bark())       ' Output: Rex says Woof!
Console.WriteLine(dog2.GetInfo())    ' Output: Bella is a 3-year-old Labrador

Encapsulation

Encapsulation is the principle of bundling data (attributes) and the methods that operate on that data into a single unit (a class), and restricting direct access to some of the object’s components.

class BankAccount:
    def __init__(self, owner, balance):
        self.__owner = owner
        self.__balance = balance

    def get_balance(self):
        return self.__balance

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount

    def withdraw(self, amount):
        if 0 < amount <= self.__balance:
            self.__balance -= amount

account = BankAccount("Alice", 1000)
account.deposit(500)
print(account.get_balance())  # Output: 1500
Class BankAccount
    Private _balance As Decimal

    Sub New(balance As Decimal)
        _balance = balance
    End Sub

    ReadOnly Property Balance As Decimal
        Get
            Return _balance
        End Get
    End Property

    Sub Deposit(amount As Decimal)
        If amount > 0 Then _balance += amount
    End Sub

    Sub Withdraw(amount As Decimal)
        If amount > 0 AndAlso amount <= _balance Then _balance -= amount
    End Sub
End Class

Inheritance

Inheritance allows a new class (the child or subclass) to inherit attributes and methods from an existing class (the parent or superclass). The child class can extend or override the inherited behaviour.

class Animal:
    def __init__(self, name, species):
        self.name = name
        self.species = species

    def get_info(self):
        return f"{self.name} is a {self.species}"

class Cat(Animal):
    def __init__(self, name):
        super().__init__(name, "Cat")

    def make_sound(self):
        return f"{self.name} says Meow!"

cat = Cat("Whiskers")
print(cat.get_info())    # Inherited: Whiskers is a Cat
print(cat.make_sound())  # Own method: Whiskers says Meow!
Class Animal
    Public Property Name As String
    Public Property Species As String

    Sub New(name As String, species As String)
        Me.Name = name
        Me.Species = species
    End Sub

    Function GetInfo() As String
        Return Name & " is a " & Species
    End Function
End Class

Class Cat
    Inherits Animal

    Sub New(name As String)
        MyBase.New(name, "Cat")
    End Sub

    Function MakeSound() As String
        Return Name & " says Meow!"
    End Function
End Class

Polymorphism

Polymorphism allows objects of different classes to be treated through the same interface. The same method name behaves differently depending on the class of the object.

class Shape:
    def area(self): return 0

class Rectangle(Shape):
    def __init__(self, w, h): self.w = w; self.h = h
    def area(self): return self.w * self.h

class Circle(Shape):
    def __init__(self, r): self.r = r
    def area(self):
        import math
        return math.pi * self.r ** 2

for shape in [Rectangle(5, 3), Circle(4)]:
    print(f"Area: {shape.area():.2f}")
# Area: 15.00
# Area: 50.27

Summary of OOP Concepts

Concept Description Benefit
Class Blueprint for creating objects Defines structure and behaviour
Object An instance of a class Represents a specific entity
Encapsulation Bundling data and methods; hiding internal state Protects data; reduces complexity
Inheritance Child class inherits from parent Code reuse; establishes relationships
Polymorphism Same method name, different behaviour Flexibility; extensibility

The spec requires you to describe the relationship between object, class and method. In an exam: a class is the template; an object is a specific instance of that class; a method is a subroutine defined within the class that describes the behaviour of objects of that class.


Low-Level vs High-Level Languages

Low-Level Languages

Low-level languages have little or no abstraction from the hardware. They deal directly with memory addresses, registers, and machine instructions.

Machine code:

  • Binary instructions (1s and 0s) the CPU executes directly.
  • Completely hardware-specific — machine code for one CPU will not run on another.
  • Extremely difficult for humans to read or write.

Assembly language:

  • Uses human-readable mnemonics (e.g. MOV, ADD, JMP) to represent machine code instructions.
  • Near one-to-one mapping to machine code — each mnemonic maps to one instruction.
  • Hardware-specific — requires an assembler to translate to machine code.
MOV AX, 5      ; Load 5 into register AX
MOV BX, 3      ; Load 3 into register BX
ADD AX, BX     ; Add BX to AX (result: 8 in AX)

High-Level Languages

High-level languages provide a high level of abstraction from the hardware.

  • Use English-like keywords and mathematical notation.
  • One statement may translate to many machine code instructions.
  • Portable — the same source code can run on different hardware platforms.
  • Easier to read, write, debug, and maintain.
  • Examples: Python, VB.NET, Java, C#.

When to Use Each

Situation Language Level Reason
Writing an operating system kernel Low-level Needs direct hardware control
Programming an embedded microcontroller Low-level Limited memory; speed-critical
Writing a device driver Low-level Must interact directly with hardware
Developing a business application High-level Productivity and maintainability matter
Writing a web application High-level Portability and rapid development needed
Performance-critical inner loop Low-level Maximum execution speed required

Comparison Table

Feature Low-Level High-Level
Abstraction Little or none High
Readability Difficult Easy
Portability Hardware-specific Mostly portable
Execution speed Very fast Slower (needs translation)
Memory control Direct Managed automatically
Development time Slow Fast

The key trade-off is control and speed (low-level) vs productivity and portability (high-level). Be ready to justify which is most suitable for a given scenario — always relate your answer to the specific requirements described.


Translators

Source code must be translated into machine code before the CPU can execute it. There are three types of translator: assemblers, compilers, and interpreters.

Assembler

  • Translates assembly language into machine code.
  • Near one-to-one translation — each mnemonic maps to one machine code instruction.
  • Output is a machine code file ready for execution.

Compiler

  • Translates the entire high-level source program into machine code all at once, before the program runs.
  • Produces a standalone executable file — the source code is not needed to run it.
  • Compilation can be slow, but the compiled program runs fast.
  • All errors are reported after the full program is analysed.

Interpreter

  • Translates and executes high-level source code one statement at a time.
  • No executable file is produced — the interpreter must be present every time the program runs.
  • Stops at the first error encountered, reporting the exact line.
  • Slower execution than compiled code, but easier to debug during development.

Comparison

Feature Assembler Compiler Interpreter
Input Assembly language High-level source High-level source
Output Machine code Executable file None (executes directly)
Translation One-to-one Whole program at once One line at a time
Execution speed Fast Fast Slow
Error reporting After assembly After full compilation At the first error line
Source needed at runtime No No Yes

Choosing the Right Translator

  • Use an interpreter during development — it stops at each error, making debugging easier.
  • Use a compiler for distribution — the compiled program runs faster and the source code is protected.
  • Use an assembler when writing low-level code for hardware control or performance-critical routines.

Be able to justify the choice of translator for a given situation. A developer might use an interpreter while writing and testing code (immediate error feedback) then compile the final product for release (faster execution, no source code exposed to end users).