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).