Skip to content

Data Structures and Control Flow

Once you can assign values and write functions, the next step is telling Python how to make decisions, repeat work, and organize collections of data.

Lists, tuples, sets and dictionaries

Python has several built-in data structures, each with a different purpose. More details can be found in the excellent Python Standard Library Documentation on sequences

Lists

Lists store ordered, changeable collections:

samples = ["steel", "glass", "polymer"]
samples.append("ceramic")
print(samples)
    ['steel', 'glass', 'polymer', 'ceramic']
samples[2] = "silica"
print(samples)
    ['steel', 'glass', 'silica', 'ceramic']

For sequences, such as lists and tuples, individual elements can be selected, called indexing. Indices always start at 0: The first element always has the index 0. The last element can be chosen by using a negative index which counts from the end: -1 is the last element.

print(samples[0])
    steel
print(samples[-1])
    ceramic

Also, sub-ranges can be selected with two indices and a colon ':' called slicing. The specified indices define the first element to include until the element specified by the second index, but not including it!

print(samples[1:3])
    ['glass', 'silica']

It is also easy to generate a list with range:

list(range(5))
    [0, 1, 2, 3, 4]

list(range(2,17,3))
    [2, 5, 8, 11, 14]

Tuples

Tuples also store ordered values, but they are typically used for fixed groups of values (read-only):

dimensions = (12.0, 4.0, 1.5)

This will give an error:

dimensions[1] = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

The above tuple can only be changed by composing a new tuple using the '+' operator for concatenation:

dimensions[:1] + (3,) + dimensions[2:]
    (12.0, 3, 1.5)

Where (3,) is a tuple with a single element and dimensions[:1] indicates a slice of the dimensions tuple from the beginning until the first element (but not the second which has index 1). While dimensions[2:] will give a slice of the tuple from the third element (index 2) until the end.

Strings

Coming back to strings for a moment, since they behave like tuples of characters in Python.

text = "Hello World"
text[3:9]
    'lo Wor'

Strings also can not by changed directly but need to be rebuild. Concatenation works with them as well:

text[:5] + "-" + text[6:]
    'Hello-World'

There is a very comprehensive library of methods that can be used with strings and come in handy very often:

# Removing whitespace, aka. 'trimming'
text = "  hello  "
text.strip()   # "hello"
text.lstrip()  # "hello  "
text.rstrip()  # "  hello"

# Splitting strings
text = "apple,banana,orange"
text.split(",")  # ["apple", "banana", "orange"]

# Joining strings
words = ["apple", "banana", "orange"]
", ".join(words)  # "apple, banana, orange"

# Checking contents
text = "hello123"
text.isalpha()   # False (contains numbers)
text.isdigit()   # False
text.isalnum()   # True

# Finding substrings
text = "hello world"
"world" in text  # True
text.find("world")  # 6
text.startswith("hello")  # True
text.endswith("world")    # True

Formatting

So called f-strings are a very powerful tool for composing messages and for debugging:

name = "Bob"
age = 23

print(f"My name is {name} and I am {age} years old.")
print(f"{name=}, {age=}")

Note: Sometimes, trouble may be caused by strings accidentally used with code designed for sequences: It will often just work but will not give the expected result. We come back to that later, after loops.

Sets

Sets store unique values (no duplicates can exist) and are useful for quick membership tests:

elements = {"Fe", "C", "Mn"}
"Fe" in elements
    True

They are very close to sets in the math and support operations like intersection, union and difference.

Performance: For very large sets of values, sets are faster than lists since the fundamental internal are different: Instead of checking each item, they use a hash function to get the memory location of values in the same way a hash table (dict) does.

Dictionaries

Dictionaries map keys to values, also called associative arrays in computer science. They are very powerful and widely used in Python:

metadata = {
    "sample_id": "A-17",
    "temperature_c": 23.5,
    "operator": "BAM",
}

They are useful whenever labels matter as much as the values themselves. They can be modified and keep the order of items as they were added. Dictionaries can have unique keys only, same as a set and in contrast to lists and tuples which may have many duplicate elements.

Conditionals

Use conditionals when your program should behave differently depending on a value:

temperature = 18

if temperature < 0:
    print("Freezing")
elif temperature < 20:
    print("Cool")
else:
    print("Warm")

This pattern appears everywhere: validating inputs, checking files, handling errors, and controlling workflow.

Loops

Loops let you repeat an operation.

for loops

Use for when iterating over a sequence:

for sample in samples:
    print(sample)

while loops

Use while when repetition depends on a condition:

count = 0

while count < 3:
    print(count)
    count += 1

Pitfall: accidentally treat a string as a sequence of items

def print_items(items):
    for item in items:
        print(item)

# Intended usage:
print_items(["apple", "banana", "cherry"])
Now accidentally pass a string:

print_items("apple")
a
p
p
l
e

Why this happens: - A string is a sequence of characters - The loop iterates over each character instead of treating "apple" as one item - Fix: use isinstance()

def print_items(items):
    if isinstance(items, str):
        items = [items]  # treat as single item
    for item in items:
        print(item)

List comprehensions

List comprehensions are a compact way to build a list from another iterable:

numbers = [1, 2, 3, 4]
squares = [n ** 2 for n in numbers]
squares
    [1, 4, 9, 16]

They are powerful, but clarity still matters. If a comprehension becomes hard to read, a normal loop is often the better choice.

Error and exception handling

Real code should fail in understandable ways. A basic try / except structure helps:

text = "42.2"  # some user input
try:
    value = (text)
except ValueError:
    print("Converting '{text}' failed!)"

Use exception handling to react to expected failure modes, such as invalid input, missing files, or parsing problems.

Recursion

Recursion means a function calls itself. It is useful for some problems, but it is not always the clearest choice for beginners:

def factorial(n):
    if n == 0:
        return 1
    return n * factorial(n - 1)

It is worth understanding the idea, even if loops remain the more common tool in daily work.

Invocation Tree Debugger

A very helpful tool for visualizing the recursion mechanism with your custom code: enter it the code box right-hand side and press play. It's source and some more example code is on its GitHub project page.

Practice ideas

  1. Create a list of numbers and print only the even ones.
  2. Build a dictionary that stores a few measurements by name.
  3. Write an if / elif / else block for a grading scale.
  4. Convert a for loop into a list comprehension and compare readability.