B2.1 Programming Fundamentals

← Back to Home

⚠ WORK IN PROGRESS
This article has not been finished yet and most likely contains mistakes or misses extrmly important stuff.
Work in progress
Contents

All examples use Python.

B2.1.1: Variables and Data Types

Variables

A variable is a named location in memory that holds data. In Python you create one just by assigning a value to it. You do not have to state the type, Python works it out.

my_number = 5
name = "Ivan"

You can add an optional type hint to make the intended type clear. Python does not enforce it, it just makes code easier to read.

my_number: int = 5

Declaring your main variables near the top of a program makes it easier to read and debug.

Data Types

The data type decides what can be stored in a variable and what you can do with it.

TypeMeaningPython example
BooleanTrue or Falseis_vegan: bool = False
IntegerWhole numberglasses: int = 0
FloatNumber with a decimal pointprice: float = 7.99
StringSequence of charactersword: str = "hello"
CharA single characterNo separate type. Use a string of length 1: initial: str = "a"

Useful tools

  • type(x) tells you the type of a variable.
  • Casting converts between types: int("5") gives 5, str(5) gives "5", float("7.99") gives 7.99.
  • input() always returns a string. Cast it if you need a number: age = int(input("Age: "))

Global and Local Variables

  • Global variable: created outside any function. It can be read anywhere in the program.
  • Local variable: created inside a function. It only exists while that function runs. Using it outside gives a NameError.
  • To change a global variable inside a function you must use the global keyword.
count = 0            # global

def add_one():
    global count     # lets the function change the global
    count = count + 1

def greet():
    message = "hi"   # local, only exists inside greet()
    print(message)

add_one()
greet()
print(count)         # 1
print(message)       # NameError: message is not defined

Local variables are safer because nothing outside the function can accidentally change them. Use globals sparingly.

Why Data Types Matter

  • Choosing a type is a decision about what data you accept and how precise it is. Storing age as an integer gives only years. A float could capture months too.
  • Python does not need types declared upfront, which makes it quick to write but slower to run than languages that do. That is why very low latency systems like trading platforms often use faster compiled languages, while Python is popular for trying out ideas quickly.

Practice Exercises

Choose and justify a data type for each:

  1. A telephone number
  2. Whether a person is vegan
  3. A password
  4. The cost of an item
  5. The number of glasses of water drunk in a day
  6. A person's middle initial
  1. String. It can start with 0 or +, may contain spaces, and you never do maths with it. As an integer the leading zero would be lost.
  2. Boolean. Only two possible values: True or False.
  3. String. Mix of letters, numbers and symbols.
  4. Float. Needs decimal places for cents.
  5. Integer. You count whole glasses.
  6. String of length 1. Python has no char type.

What does this print, and why?

score = 10

def update():
    score = 50
    print(score)

update()
print(score)

It prints 50 then 10.

Inside update(), score = 50 creates a new local variable. It does not touch the global one. To change the global you would need global score at the start of the function.

Ninja Notes:
  • input() always gives a string. Forgetting to cast is one of the most common bugs.
  • Phone numbers are strings, not integers. This comes up a lot.
  • Local variables disappear when the function ends. Globals need the global keyword to be changed inside a function.

B2.1.2: Substrings

Strings and Indexes

A string is a sequence of characters. Every character has an index, starting at 0. Negative indexes count from the end.

CharacterOld(space)Town
Index01234567
Negative−8−7−6−5−4−3−2−1

String Operations

All examples use s = "Old Town".

OperationResultWhat it does
s[4]"T"Character at an index
s[3:6]" To"Slice from index 3 up to, but not including, 6
s[:3]"Old"Slice from the start
s[4:]"Town"Slice to the end
len(s)8Number of characters
"Old" in sTrueChecks if a substring exists
s.find("T")4Index of the first match. Returns −1 if not found
s.rfind("own")5Index of the last match
s.replace("Old", "New")"New Town"Replaces every match
s.split()["Old", "Town"]Splits on spaces into a list
s.upper() / s.lower()"OLD TOWN" / "old town"Changes case
s + " is old""Old Town is old"Concatenation: joining strings

Things to remember

  • Strings are immutable. Methods like replace() return a new string, they do not change the original. Save the result: s = s.replace("Old", "New")
  • A slice's end index is not included. s[3:6] gives indexes 3, 4 and 5.
  • You can only concatenate strings with strings. "Age: " + 17 crashes. Use "Age: " + str(17).
  • Python has many more string methods. The full list is in the Python documentation, which describes the language's API (the set of methods and rules you can use).

Most programs take user input as strings, so being comfortable with string handling makes your programs much more useful.

Practice Exercises

With s = "Computer Science", what does each give?

  1. s[0]
  2. s[9:16]
  3. s.find("S")
  4. len(s)
  5. s.split()
  1. "C"
  2. "Science"
  3. 9
  4. 16
  5. ["Computer", "Science"]

Write a program that creates a starting password by taking the first three letters of a person's surname, making them lowercase, and adding special characters and the length of the surname.

surname = input("Enter your surname: ")
password = surname[:3].lower() + "#" + str(len(surname)) + "!"
print("Your starting password is", password)

# Input: Kruchinin
# Output: Your starting password is kru#9!

Note the str() around len(surname). Without it you would be adding a string to an integer and the program would crash.

Ninja Notes:
  • Indexes start at 0. The slice end is never included.
  • find() returns −1 when nothing is found. It does not crash.
  • Strings cannot be changed in place. Always store the result of a string method.

B2.1.3: Exception Handling

What is Exception Handling?

An exception is unexpected behaviour while a program runs. If it is not handled, the program crashes. Exception handling lets the program deal with the problem and carry on.

Three Points of Failure

FailureWhat happensHow to prevent it
Unexpected inputThe user enters something the program was not expecting, like letters when a number was needed.Validation: range checks, dropdown menus with only valid options, checking required content (an @ in an email).
Resource unavailableA file, library, sensor or actuator the program needs is missing or was not imported properly.Wrap access in try/except and give a clear message instead of crashing.
Logic errorsThe code runs but gives wrong results. Causes: wrong brackets in calculations, reusing a variable name, infinite loops, misused Boolean operators.Thorough testing with lots of different data, including edge cases.

try, except, finally

Risky code goes in the try block. If it fails, the except block runs instead of the program crashing.

pets = ["Ben", "Leo", "Zeppo"]
try:
    print(pets[5])
except IndexError:
    print("An index out of range error has occurred")

# Output: An index out of range error has occurred

A finally block always runs, whether there was an error or not. Useful for cleanup like closing files.

try:
    print(pets[5])
except IndexError:
    print("An error has occurred")
finally:
    print("The program has ended safely")

# Output:
# An error has occurred
# The program has ended safely

Common exception types

ExceptionWhen it happens
ValueErrorRight type but wrong value, e.g. int("abc")
IndexErrorList index out of range
KeyErrorDictionary key does not exist
ZeroDivisionErrorDividing by zero
TypeErrorWrong type, e.g. "a" + 1
FileNotFoundErrorOpening a file that does not exist
NameErrorUsing a variable that does not exist

Catching a specific exception is better than a bare except:, because you know exactly what went wrong and you do not hide other bugs.

Input Validation Example

This keeps asking until the user enters a valid age. It handles both wrong type and wrong range.

while True:
    try:
        age = int(input("Enter your age: "))
        if 0 <= age <= 120:
            break
        print("Age must be between 0 and 120")
    except ValueError:
        print("Please enter a whole number")

print("Age saved:", age)
Garbage in, garbage out: bad input gives bad output. Exception handling stops bad data getting in, stops wrong files being read, and keeps programs running. Good developers always think about the end user and how they might misuse the program.

Practice Exercises

try:
    x = int("abc")
    print("Converted")
except ValueError:
    print("Not a number")
finally:
    print("Done")
Not a number
Done

int("abc") raises a ValueError, so "Converted" is never printed. The finally block always runs.

Write a program that asks for two numbers and prints the first divided by the second. It must not crash if the user types letters or enters 0 as the second number.

try:
    a = float(input("First number: "))
    b = float(input("Second number: "))
    print("Result:", a / b)
except ValueError:
    print("Please enter numbers only")
except ZeroDivisionError:
    print("You cannot divide by zero")

Two separate except blocks give a clear message for each problem.

You are building a program for teachers to store student data. What information would they store, and what validation or exception handling would it need?

Many valid answers. For example:

  • Data: name (string), student ID (string), year group (integer), grades (list of integers), email (string).
  • Validation: grades between 1 and 7, year group from a dropdown, email must contain @, ID must be a set length.
  • Exception handling: handle the data file not being found, catch non numeric grades with ValueError, use finally to close the file safely.
Ninja Notes:
  • try holds the risky code, except handles the error, finally always runs.
  • Know the three points of failure: unexpected input, unavailable resources, logic errors.
  • Logic errors do not raise exceptions. Only testing finds them.

B2.1.4: Debugging Techniques

What is Debugging?

Using tools and techniques to find and fix errors in your code. Always know what inputs your code takes and what output it should give. If the results are wrong, use these techniques to track down why. You will use all of them in your IA.

1. Trace Tables

Track the value of every variable at each step to check it changes the way you expect. Example: counting warm days (over 20°C).

temperature = [23.7, 19.9, 23.8, 18.8, 12.5, 24.0]
warm_days = 0
for temp in temperature:
    if temp > 20:
        warm_days = warm_days + 1
print("The number of warm days this month:", warm_days)
Iterationtemptemp > 20?warm_daysOutput
023.7True1
119.9False1
223.8True2
318.8False2
412.5False2
524.0True33
Watch the list syntax. Writing 19,9 instead of 19.9 creates two separate items (19 and 9), which silently breaks the result. A trace table would catch this straight away.

2. Print Statements

Add print lines to show which loop, iteration or branch the program is in and what the variables hold at that moment. This shows whether the program runs the way you expect. Example: a selection sort with debug prints.

to_sort = [4, 56, 23, 2938, 29, 485, 2]

for i in range(len(to_sort) - 1):
    min_index = i
    print("Outer loop, i =", i)
    for j in range(i + 1, len(to_sort)):
        print("  Inner loop, j =", j)
        if to_sort[j] < to_sort[min_index]:
            min_index = j
            print("    New smallest at index", min_index)
    to_sort[i], to_sort[min_index] = to_sort[min_index], to_sort[i]

print(to_sort)   # [2, 4, 23, 29, 56, 485, 2938]

The output is a running log of exactly what the code is doing. Remove the debug prints once the bug is fixed.

3. Breakpoints

A breakpoint pauses the program at a chosen line so you can inspect every variable at that moment. In most IDEs (VS Code, PyCharm, Thonny) you click in the margin next to a line number to set one. You can also add breakpoint() directly in Python code.

Useful after changing an algorithm, or for checking one section of a large program without reading hundreds of lines of output. In the sort above, a breakpoint on min_index = j lets you see the values each time a new smallest item is found.

4. Step by Step Execution

The IDE runs the code one line at a time. It highlights the current line and shows a live table of all variable values.

  • Step over: run the current line, including any function it calls, as one step.
  • Step into: go inside the function being called and run it line by line.
  • Step out: finish the current function and return to where it was called.
If a value is not what you expected at a step, that is where to look.

Practice Exercises

nums = [3, 8, 1, 6]
total = 0
for n in nums:
    if n > 2:
        total = total + n
print(total)

Draw a trace table with columns n, n > 2?, total and Output.

nn > 2?totalOutput
3True3
8True11
1False11
6True1717

This should print the average of the list, but it prints the wrong number. Find the bug.

marks = [6, 7, 5, 4]
total = 0
for m in marks:
    total = total + m
    average = total / len(marks)
    print(average)

The print (and the average calculation) are indented inside the loop, so it prints a running value four times instead of one final average. Print statements make this obvious: you see 1.5, 3.25, 4.5, 5.5.

marks = [6, 7, 5, 4]
total = 0
for m in marks:
    total = total + m
average = total / len(marks)
print(average)   # 5.5
Ninja Notes:
  • Know all four techniques by name: trace tables, print statements, breakpoints, step by step execution.
  • Trace tables: write a new row every time a variable changes, and check the last pass of the loop carefully.
  • In a selection sort using <, the loop is finding the smallest value, so the list is sorted ascending. Read the comparison, not just the variable name.