Part 2: Core Programming Concepts
| Site: | Learn at Merupu AI |
| Course: | Programming Concepts |
| Book: | Part 2: Core Programming Concepts |
| Printed by: | Guest user |
| Date: | Tuesday, 27 October 2026, 7:30 AM |
Description
Part 2: Core Programming Concepts — read through each chapter in order. Diagrams, tables and worked examples included.
1. Variables & Data Types
Variables: labelled boxes for your data
A variable is a named place in the computer's memory where your program stores a value it needs to remember. The clearest way to picture it is a labelled box: the label is the variable's name, and whatever you put inside is its value. When you want the value later, you refer to the box by its label rather than its physical location.
Declaration and assignment
Two things happen when you create a variable. Declaration brings the name into existence; assignment places a value in it using the assignment operator =. Read = as "gets" or "becomes", never as mathematical equality.
age = 30 # create 'age', put 30 in it
price = 19.99 # a decimal number
name = "Ada" # a piece of text
is_active = True # a yes/no value
age = age + 1 # read 30, add 1, store 31 back
The last line shows why = is not equality: age = age + 1 is nonsense as maths, but as an instruction it means "take the current value, add one, and store the result back in the same box." After it runs, age holds 31.
Data types
Every value has a data type — a category that tells the computer what the value is and what you may do with it. You can add two integers, but "adding" two booleans makes no sense. Types also decide how much memory a value needs and how it is stored. The core types nearly every language shares are:
| Type | Stores | Example values | Typical use |
|---|---|---|---|
| Integer | Whole numbers | -4, 0, 42, 1000 | Counts, ages, quantities, indexes |
| Float | Numbers with a fractional part | 3.14, -0.5, 19.99 | Prices, measurements, averages |
| String | Text (a sequence of characters) | "hello", "Ada", "Z" | Names, messages, labels |
| Boolean | One of two truth values | True, False | Flags, conditions, on/off state |
| Null / None | "No value at all" | None, null, nil | Missing or not-yet-set data |
Static vs dynamic typing
Languages differ in when they check types. In statically typed languages (Java, C#) you state a variable's type up front and it cannot change. In dynamically typed languages (Python, JavaScript) a variable simply takes the type of whatever you assign, and that can change as the program runs. Both styles track types; they just enforce them at different moments.
Naming variables well
Choose names that say what the value means: total_price beats tp, and user_count beats x. Good names turn code into something a human can read like a sentence. Most languages require names to start with a letter, allow letters, digits and underscores, and are case-sensitive — so Age and age are two different boxes.
Key idea: a variable is a named, re-usable container; its type describes the kind of value it holds and the operations that value supports.
2. Operators & Expressions
Making the computer calculate and decide
An operator is a symbol that performs an operation on one or more values, called operands. When you combine values and operators you form an expression — something the computer evaluates down to a single result. 3 + 4 is an expression that evaluates to 7; price * quantity is an expression whose result depends on the current values of those variables.
Arithmetic operators
These do ordinary maths. Note the two "division-like" operators many languages provide: regular division and remainder (modulo), which gives what is left over.
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ | Addition | 7 + 2 | 9 |
- | Subtraction | 7 - 2 | 5 |
* | Multiplication | 7 * 2 | 14 |
/ | Division | 7 / 2 | 3.5 |
% | Modulo (remainder) | 7 % 2 | 1 |
** | Exponent (power) | 7 ** 2 | 49 |
Modulo is more useful than it looks: n % 2 == 0 tests whether n is even, and modulo is how you wrap around clock faces, grids and buffers.
Comparison operators
Comparison operators ask a question about two values and always produce a boolean (true or false): == (equal), != (not equal), <, >, <=, >=. The single most common beginner bug is writing = (assignment) where you meant == (comparison).
age = 20
age == 18 # False (is it equal to 18?)
age >= 18 # True (18 or more?)
age != 18 # True (not equal to 18?)
Logical operators
Logical operators combine boolean values so you can express compound conditions: AND is true only when both sides are true; OR is true when at least one side is true; NOT flips a value. The full behaviour is captured in a truth table:
| A | B | A AND B | A OR B | NOT A |
|---|---|---|---|---|
| True | True | True | True | False |
| True | False | False | True | False |
| False | True | False | True | True |
| False | False | False | False | True |
age = 25
has_ticket = True
age >= 18 and has_ticket # True — both conditions hold
Assignment operators
Besides plain =, most languages offer compound assignment that updates a variable in place: x += 3 means x = x + 3. The family includes -=, *=, /= and more. They are shorthand, not new powers.
Operator precedence
When an expression mixes operators, the computer follows precedence rules — essentially the maths "BODMAS/PEMDAS" order. Multiplication and division happen before addition and subtraction, and comparisons happen before logical operators. When in doubt, add parentheses: they are free, and they make intent unmistakable.
2 + 3 * 4 # 14, not 20 — * runs first
(2 + 3) * 4 # 20 — parentheses force the addition first
age >= 18 and age < 65 # comparisons evaluate before 'and'
Key idea: operators turn values into new values; an expression is any combination that evaluates to a single result, governed by precedence.
3. Input & Output
A program as input → process → output
Almost every useful program follows the same shape: it takes some input, processes it according to rules you write, and produces output. This is the IPO model, and keeping it in mind helps you structure any program: ask "what comes in, what must I compute, what goes out?"
Output: showing results
Output is how a program communicates with the outside world. The simplest form is printing text to the screen. Early in your learning, printing is also your most valuable debugging tool: when you are unsure what a variable holds, print it and look.
print("Hello, world!")
name = "Ada"
print("Hello, " + name) # Hello, Ada
print("Your score is", 95) # Your score is 95
Input: reading from the user
Input lets the program receive data while it runs, so the same code can work on different values each time. A classic beginner trap: input from the keyboard almost always arrives as a string, even when it looks like a number. If you need to do arithmetic, you must convert (cast) it to the right type first.
name = input("What is your name? ") # always text
print("Hi " + name)
age_text = input("Your age? ") # "30" as text
age = int(age_text) # convert to integer
print("Next year you will be", age + 1) # now arithmetic works
If you forget the conversion and try age_text + 1, the program fails because you cannot add a number to text. This single mistake accounts for a large share of early errors, so build the habit of asking "what type is this really?"
Other sources and destinations
The keyboard and screen are only the most visible channels. Input can also come from files, sensors, databases, or network requests; output can go to files, other programs, or back across the network. The IPO shape stays the same — only the channels change. A program that reads a file, transforms its contents and writes a new file is still just input → process → output.
Formatting output
Clear output matters as much as correct computation. Most languages let you build readable messages by inserting values into a template. Rounding a price to two decimals, labelling a number, or lining up columns all make the difference between output a person can use and a wall of raw values.
price = 19.5
print(f"Total: ${price:.2f}") # Total: $19.50
Key idea: structure programs as input → process → output; always know the type of the data crossing each boundary, and convert at the edges.
4. Conditionals / Decision Making
Letting a program choose
Without decisions a program would do exactly the same thing every time. Conditionals let it branch: run one block of code when a condition is true, and perhaps a different block when it is false. The condition is any expression that evaluates to a boolean — true or false.
if, else
The simplest conditional runs a block only when a condition holds. Add else to specify what happens otherwise. Notice how indentation (or braces, in other languages) marks which statements belong to each branch.
age = 20
if age >= 18:
print("You may enter.")
else:
print("Sorry, you are too young.")
else if: choosing among many paths
Often there are more than two outcomes. Chain conditions with else if (written elif in Python). The computer checks each condition in order and runs the first one that is true, skipping the rest — so order matters.
score = 82
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
print(grade) # B
If you reversed the order and tested score >= 70 first, everyone above 70 would get a C, because the first matching branch wins. Put the most specific or highest conditions first.
Combining conditions
Use the logical operators from the previous chapter to build richer tests. The table shows how a two-part condition behaves:
| age >= 18 | has_ticket | age >= 18 AND has_ticket | Outcome |
|---|---|---|---|
| True | True | True | Enter |
| True | False | False | Blocked |
| False | True | False | Blocked |
| False | False | False | Blocked |
Nested conditions
A conditional can live inside another conditional. This is useful when a second question only makes sense after the first is answered. Keep nesting shallow — deeply nested ifs become hard to read, and can often be flattened with and or an early return.
if logged_in:
if is_admin:
print("Admin dashboard")
else:
print("User dashboard")
else:
print("Please log in")
Key idea: conditionals steer the flow of a program using boolean expressions; elif chains pick the first true branch, and clear conditions beat clever ones.
5. Loops & Iteration
Repeating work without repeating yourself
A loop runs a block of code again and again. This is how programs process every item in a list, keep asking until input is valid, or count from 1 to a million without you typing a million lines. Running the body once is called an iteration.
The while loop
A while loop repeats as long as a condition stays true. It checks the condition, runs the body, then checks again. Use it when you do not know in advance how many times you will repeat — for example, "keep going until the user types 'quit'."
count = 1
while count <= 3:
print("Hello", count)
count = count + 1 # move toward the exit!
print("Done")
Line by line: count starts at 1, the condition 1 <= 3 is true so the body prints and increments to 2, then 3, then 4. At 4 <= 3 the condition is false and the loop ends. The variable that drives the loop is the loop counter.
Infinite loops
If the condition never becomes false, the loop runs forever — an infinite loop. The usual cause is forgetting to change the counter. Always make sure something inside the body moves you toward the exit condition.
# BUG: count never changes, so this never stops
count = 1
while count <= 3:
print("stuck") # prints forever
The for loop
A for loop is built for a known number of repetitions or for walking through every item in a collection. It bundles the setup, condition and update into one tidy line, which makes "off-by-one" mistakes rarer.
for i in range(1, 4): # i takes 1, then 2, then 3
print("Hello", i)
for letter in "cat": # iterate over each character
print(letter) # c, a, t
for vs while
| Aspect | for loop | while loop |
|---|---|---|
| Best when | You know the count, or iterate a collection | You repeat until a condition changes |
| Counter | Managed automatically | You manage it by hand |
| Infinite-loop risk | Lower | Higher (easy to forget the update) |
| Typical use | "for each student, print the name" | "while not logged in, keep asking" |
break and continue
Two statements fine-tune a loop. break exits the loop immediately. continue skips the rest of the current iteration and jumps to the next one. Use them sparingly — a clear condition is usually better than a loop peppered with jumps.
for n in range(1, 100):
if n == 5:
break # stop entirely at 5
if n % 2 == 0:
continue # skip even numbers
print(n) # prints 1, 3
Key idea: loops repeat work; for suits a known number of steps, while suits "until something changes," and every loop must be able to end.
6. Functions & Modular Programming
Packaging logic into reusable units
A function is a named block of code that performs one job. You define it once and call it as many times as you like. Functions are the heart of modular programming: instead of one giant script, you build small, named pieces that each do one thing well and fit together.
Defining and calling
To define a function you give it a name, list the inputs it expects, and write the body. To use it, you call it by name and supply values.
def greet(name): # define; 'name' is a parameter
print("Hello, " + name)
greet("Ada") # call; "Ada" is the argument
greet("Grace") # reuse with a different argument
Parameters and arguments
A parameter is the placeholder named in the definition; an argument is the actual value you pass when calling. A function can take several parameters, and the order you pass arguments usually matters.
Return values
Printing shows something to the user; returning hands a value back to the code that called the function, so it can be stored or used in further calculation. A function that returns is like a small machine: inputs go in, a result comes out.
def area(width, height):
return width * height
a = area(4, 3) # a is 12
total = area(4, 3) + area(2, 2) # 12 + 4 = 16
The difference between print and return trips up many beginners: a printed value appears on screen but is gone; a returned value can be captured in a variable and reused.
Scope
Scope is the region of a program where a name is visible. Variables created inside a function are local: they exist only while the function runs and cannot be seen from outside. This isolation is a feature — it means a function cannot accidentally clobber variables elsewhere, so you can reason about it on its own.
def add_tax(price):
tax = price * 0.1 # 'tax' is local
return price + tax
print(add_tax(100)) # 110
# print(tax) -> ERROR: 'tax' does not exist out here
DRY: Don't Repeat Yourself
The deepest reason for functions is DRY — "Don't Repeat Yourself." If the same logic appears in three places and you later find a bug, you must fix it three times and will probably miss one. Put the logic in a function and call it three times, and there is a single place to read, test and correct. Good functions have clear names, do one job, and are short enough to understand at a glance.
Key idea: functions package logic behind a name so it can be reused and tested in isolation; parameters feed them, return values hand results back, and scope keeps their internals private.
7. Data Structures: Arrays & Lists
Holding many values together
A single variable holds one value. But programs constantly deal with collections: all the scores in a class, every item in a cart, each line of a file. An array (often called a list) stores many values in one ordered container under a single name, so you can treat the whole group as a unit and reach any member when you need it.
Indexing starts at zero
Each slot has a position number called its index. The crucial rule, shared by most languages, is that indexing starts at 0: the first item is at index 0, the second at index 1, and the last is at index length − 1. This "zero-based" counting feels odd at first and is the source of countless beginner bugs, so burn it in now.
scores = [90, 85, 72, 95, 60]
print(scores[0]) # 90 (first)
print(scores[4]) # 60 (fifth and last)
print(len(scores)) # 5 (how many items)
# scores[5] -> ERROR: out of range (there is no index 5)
Iterating over a list
The natural partner of a list is a loop. A for loop can visit every element in turn, which is how you sum, search, filter or transform a collection.
scores = [90, 85, 72, 95, 60]
total = 0
for s in scores:
total = total + s
print("Average:", total / len(scores)) # Average: 80.4
Common operations
| Operation | What it does | Example |
|---|---|---|
| Access | Read the item at an index | scores[2] → 72 |
| Update | Replace an item | scores[2] = 75 |
| Append | Add to the end | scores.append(88) |
| Remove | Delete an item | scores.remove(60) |
| Length | Count the items | len(scores) → 5 |
| Contains | Test membership | 85 in scores → True |
Maps / dictionaries (a quick look)
Lists find things by numeric position. Sometimes you want to find things by a meaningful key instead — a phone book looks people up by name, not by row number. For that there is the map (also called a dictionary, hash or associative array), which stores key → value pairs.
ages = {"Ada": 36, "Grace": 85}
print(ages["Ada"]) # 36 — look up by key, not index
ages["Linus"] = 54 # add a new pair
Key idea: arrays/lists group ordered values under one name and are accessed by zero-based index; loop over them to process every element, and reach for a map when a named key fits better than a position.
8. Strings & Text Processing
Working with text
A string is a sequence of characters — letters, digits, spaces, punctuation — treated as a single value. Text is everywhere in software: names, messages, file contents, web pages, user input. Knowing how to pick strings apart and reassemble them is one of the most practical everyday skills.
Characters and indexing
A string behaves much like the array from the previous chapter: each character sits at a zero-based index, and the string has a length. You can read a single character by its position, and reach the last character at index length − 1.
word = "HELLO"
print(word[0]) # 'H' (first character)
print(word[4]) # 'O' (last)
print(len(word)) # 5
Concatenation: joining strings
Putting strings together end-to-end is called concatenation, usually written with +. Remember that + means "add" for numbers but "join" for strings — and you cannot mix the two without converting, because "Age: " + 30 is an error, while "Age: " + str(30) works.
first = "Ada"
last = "Lovelace"
full = first + " " + last # "Ada Lovelace"
label = "Age: " + str(30) # convert the number first
Slicing: taking a piece
A slice extracts a substring by giving a start and end position. As with ranges, the start is included and the end is excluded, so word[0:3] gives the first three characters.
word = "programming"
print(word[0:4]) # "prog" (indices 0,1,2,3)
print(word[4:]) # "ramming" (from 4 to the end)
Common operations
Languages provide a rich toolkit for text. The names vary slightly, but the ideas are universal:
| Operation | Meaning | Example | Result |
|---|---|---|---|
| length | Number of characters | len("cat") | 3 |
| upper / lower | Change case | "cat".upper() | "CAT" |
| replace | Swap one piece for another | "a-b".replace("-", ":") | "a:b" |
| split | Break into a list on a separator | "a,b,c".split(",") | ["a","b","c"] |
| strip | Trim surrounding whitespace | " hi ".strip() | "hi" |
| find / index | Locate a substring | "hello".find("ll") | 2 |
Immutability
In many languages strings are immutable: operations like upper() or replace() do not change the original string — they return a new one. If you want to keep the result, assign it to a variable. Beginners often call a method and wonder why "nothing happened"; the answer is that the new string was thrown away because it was never stored.
name = "ada"
name.upper() # makes "ADA" but discards it
print(name) # still "ada"
name = name.upper() # now keep it
print(name) # "ADA"
Splitting and joining: a everyday pattern
A huge amount of real text work is "split a line into parts, process the parts, join them back." Reading a CSV line, parsing a date, or cleaning user input all follow this shape.
line = "Ada,36,London"
parts = line.split(",") # ["Ada", "36", "London"]
name = parts[0] # "Ada"
age = int(parts[1]) # 36
Key idea: strings are indexed sequences of characters; join them with concatenation, carve them with slicing, transform them with built-in operations, and remember that many string operations return new strings rather than changing the original.
9. Errors, Debugging & Testing
Things will go wrong — and that is normal
Every programmer, from day one to decades in, writes code that does not work the first time. The skill that separates the frustrated from the productive is not "never make mistakes" — it is finding and fixing them calmly. That starts with understanding the three kinds of errors.
The three kinds of errors
| Type | When it happens | Symptom | Example |
|---|---|---|---|
| Syntax error | Before the program runs | Code won't start; the language can't parse it | Missing ), misspelled keyword, bad indentation |
| Runtime error | While the program runs | Program crashes partway with an error message | Divide by zero, index out of range, converting "abc" to a number |
| Logic error | Program runs fine | No crash, but the answer is wrong | Using + where you meant -; wrong loop bound |
Syntax errors are the friendliest: the computer refuses to run and usually points near the problem. Runtime errors are louder — the program stops midway and prints a message. Logic errors are the sneakiest, because everything looks fine; the program just produces the wrong result, and only testing reveals it.
Reading error messages
Beginners often panic at a wall of red text and ignore it. Do the opposite: read it carefully. An error message typically tells you the type of error, a short description, and the line number where it occurred. Start at the bottom of a stack trace — that is usually closest to your own code.
numbers = [1, 2, 3]
print(numbers[5])
# IndexError: list index out of range <- type + description
# line 2, in <module> <- where it happened
Debugging strategies
Debugging is detective work: form a theory, test it, narrow down. A few reliable techniques:
- Print the state. Add
printstatements to show variable values at key points. Often the value is simply not what you assumed. - Read the code aloud. Explaining it line by line (even to a rubber duck) exposes wrong assumptions fast.
- Isolate the problem. Comment out parts until the error disappears; the last thing you removed is near the cause.
- Change one thing at a time. If you change five things and it works, you don't know why — and that knowledge is the whole point.
- Check the boundaries. Off-by-one, empty lists, and the very first/last item cause a huge share of bugs.
Testing: proving it works
Running a program once and seeing it behave is not proof it is correct. Testing means feeding known inputs and checking you get the expected outputs — especially the tricky cases. A simple test is just a comparison you can run again whenever you change the code.
def add(a, b):
return a + b
# simple checks
print(add(2, 3) == 5) # True — normal case
print(add(-1, 1) == 0) # True — negatives
print(add(0, 0) == 0) # True — edge case
Think about edge cases: empty input, zero, negative numbers, the largest value, the first and last elements. These corners are where bugs hide. Writing tests before you are "done" turns vague confidence into evidence.
Handling errors gracefully
Some errors you can anticipate and recover from rather than crash. Most languages offer a try/catch (in Python, try/except) mechanism so the program can respond to a problem — for example, asking the user to re-enter a number instead of dying.
try:
age = int(input("Age? "))
print("Next year:", age + 1)
except ValueError:
print("That wasn't a number — please try again.")
Key idea: distinguish syntax, runtime and logic errors; read messages instead of fearing them; debug methodically by changing one thing at a time; and test with known inputs and edge cases to turn "it seems to work" into "I can prove it works."
10. Introduction to Object-Oriented Thinking
Modelling the world as objects
So far we have kept data (variables, lists) and behaviour (functions) in separate places. Object-oriented programming (OOP) bundles them together into objects: self-contained units that hold both the data describing something and the operations that act on that data. This mirrors how we think about the real world — a car has a colour and speed, and can accelerate and brake.
Class vs object
The two core words confuse everyone at first, so anchor them with an analogy. A class is a blueprint — a general description of what every dog has and can do. An object (or instance) is a specific thing built from that blueprint — this dog named Rex. One class, many objects: the blueprint for a house is drawn once, but you can build many houses from it, each with its own address and colour.
Attributes and methods
An object's data are its attributes (sometimes called properties or fields) — name, breed, age. Its behaviours are its methods — functions that belong to the object, like bark() or sit(). A method can read and change the object's own attributes, which is exactly the point of bundling them together.
class Dog:
def __init__(self, name, breed):
self.name = name # an attribute
self.breed = breed
def bark(self): # a method
print(self.name + " says woof!")
rex = Dog("Rex", "Collie") # create an object
bella = Dog("Bella", "Boxer") # another, independent object
rex.bark() # Rex says woof!
print(bella.breed) # Boxer
Notice that rex and bella are built from the same class yet hold their own values. Calling rex.bark() uses Rex's name, not Bella's, because the method works on the specific object it is called on.
Encapsulation (a gentle first look)
Encapsulation is the principle of keeping an object's data and the code that manages it together, and controlling access from outside. The idea: an object exposes a tidy set of methods — a public "control panel" — while hiding its inner workings. You can drive a car through the steering wheel and pedals without knowing how the engine works; if the engine is redesigned, your driving does not change. In code, this means other parts of the program interact through methods rather than poking at attributes directly, so you are free to change the internals later without breaking everyone else.
Why think in objects?
- Organisation: related data and behaviour live in one place instead of scattered across the program.
- Reuse: define a class once, create as many objects as you need.
- Modelling: code mirrors the real-world things it represents, so it is easier to reason about.
- Maintainability: encapsulation lets you change an object's internals without disturbing the rest of the system.
OOP is a large topic with more ideas ahead — inheritance, polymorphism and more — but it all rests on this foundation: an object is data plus behaviour, built from a class blueprint, interacted with through its methods.
Key idea: a class is a blueprint describing attributes (data) and methods (behaviour); objects are concrete instances of that blueprint, and encapsulation keeps each object's data and logic bundled behind a clean interface.