Lists, Loops & Functions — With Runnable Examples
This page walks through every lecture topic from Week 3 with working code examples. Read each section, study the examples, then try modifying them yourself in VS Code or IDLE.
.py file and run it. Change
the values and see what happens — that's the fastest way to build intuition for how these concepts work.
A list is a container that holds multiple values in order. Instead of creating a separate variable for every item, you store them all in one place.
# Square brackets [ ] create a list
fruits = ["apple", "banana", "cherry"]
scores = [95, 82, 77, 91, 88]
mixed = ["Alice", 42, True, 3.14] # lists can hold any types
print(fruits) # ['apple', 'banana', 'cherry']
print(scores) # [95, 82, 77, 91, 88]
print(len(fruits)) # 3 -- number of items
Every item has a numbered position called an index, starting at 0.
colors = ["red", "green", "blue", "yellow"]
print(colors[0]) # red -- first item
print(colors[2]) # blue -- third item
print(colors[-1]) # yellow -- last item (negative counts from the end)
print(colors[-2]) # blue -- second-to-last
list[0], not list[1]. Forgetting this is one of the most common beginner bugs.
Lists aren't fixed — you can add, remove, and reorganize items after you create them.
shopping = ["milk", "eggs"]
shopping.append("bread")
shopping.append("butter")
print(shopping) # ['milk', 'eggs', 'bread', 'butter']
print(len(shopping)) # 4
shopping = ["milk", "eggs", "bread", "butter"]
shopping.remove("eggs") # removes the first "eggs" it finds
print(shopping) # ['milk', 'bread', 'butter']
# pop() removes and returns the last item (or item at an index)
last = shopping.pop()
print(last) # butter
print(shopping) # ['milk', 'bread']
Slicing uses list[start:end] — it includes start but excludes end.
numbers = [10, 20, 30, 40, 50, 60]
print(numbers[1:4]) # [20, 30, 40] -- index 1 up to (not including) 4
print(numbers[:3]) # [10, 20, 30] -- from the beginning up to index 3
print(numbers[3:]) # [40, 50, 60] -- from index 3 to the end
print(numbers[-2:]) # [50, 60] -- last two items
nums = [3, 1, 4, 1, 5, 9, 2, 6]
print(sorted(nums)) # [1, 1, 2, 3, 4, 5, 6, 9] -- new sorted list
print(nums) # original unchanged
nums.sort() # sorts in place (modifies the original)
print(nums) # [1, 1, 2, 3, 4, 5, 6, 9]
print(sum(nums)) # 31
print(min(nums)) # 1
print(max(nums)) # 9
print(1 in nums) # True -- check if a value exists
print(7 in nums) # False
Here's a realistic example that shows why lists are so much better than separate variables. Imagine tracking quiz scores for a class of 8 students.
Without lists — painful and impossible to scale:
# What if you had 30 students? You'd have 30 variables.
# And you'd have to write every calculation by hand.
score1 = 88
score2 = 73
score3 = 95
score4 = 61
score5 = 79
score6 = 82
score7 = 55
score8 = 91
total = score1 + score2 + score3 + score4 + score5 + score6 + score7 + score8
average = total / 8
# How would you find the highest? Check every one manually...
# How would you filter for failing grades? More manual comparisons...
# This approach completely falls apart at any real scale.
With a list — clean, scalable, and powerful:
scores = [88, 73, 95, 61, 79, 82, 55, 91]
# These three lines do everything the 15 lines above tried to do:
average = sum(scores) / len(scores)
highest = max(scores)
failing = []
for score in scores:
if score < 70:
failing.append(score)
print(f"Class average: {average:.1f}") # Class average: 78.0
print(f"Highest score: {highest}") # Highest score: 95
print(f"Failing scores: {failing}") # Failing scores: [61, 55]
print(f"Students failing: {len(failing)}") # Students failing: 2
# Want to add a 9th student? One line:
scores.append(67)
# The average, highest, and failing recalculate automatically
# because they work on the list — not hardcoded values.
A for loop automatically goes through every item in a list one at a time. This is how you
automate repetitive tasks.
for loop. The loop runs it once for each item automatically.
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(f"I like {fruit}")
# Output:
# I like apple
# I like banana
# I like cherry
players = ["Alice", "Bob", "Carol"]
for i, player in enumerate(players):
print(f"Player {i + 1}: {player}")
# Output:
# Player 1: Alice
# Player 2: Bob
# Player 3: Carol
for i in range(5):
print(i) # 0 1 2 3 4
for i in range(1, 6):
print(i) # 1 2 3 4 5
for i in range(0, 10, 2):
print(i) # 0 2 4 6 8 (step of 2)
scores = [55, 72, 88, 44, 91]
passing = []
for score in scores:
if score >= 60:
passing.append(score)
print(passing) # [72, 88, 91]
A 2D list is a list of lists — each inner list is a row. This is how you represent a grid, game board, or table of data.
grid[row][col]. Think of it
as: first pick the row, then pick the column within that row.
# A 3x3 TicTacToe board
board = [
[" ", " ", " "], # row 0
[" ", " ", " "], # row 1
[" ", " ", " "], # row 2
]
board[0][0] = "X" # top-left
board[1][1] = "O" # center
board[2][2] = "X" # bottom-right
print(board[0]) # ['X', ' ', ' ']
print(board[1]) # [' ', 'O', ' ']
print(board[2]) # [' ', ' ', 'X']
board = [
["X", " ", "O"],
[" ", "X", " "],
["O", " ", "X"],
]
for row in board:
print("|" + "|".join(row) + "|")
# Output:
# |X| |O|
# | |X| |
# |O| |X|
grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
for row_index in range(3):
for col_index in range(3):
value = grid[row_index][col_index]
print(f"grid[{row_index}][{col_index}] = {value}")
# Output:
# grid[0][0] = 1
# grid[0][1] = 2
# grid[0][2] = 3
# ... (continues for all 9 cells)
A function is a named block of code that you write once and can run (call) as many times as you need. Functions are the primary tool for keeping code organized and avoiding repetition.
def greet():
print("Hello! Welcome to the program.")
print("Let's get started.")
# The function does nothing until you call it:
greet() # Hello! Welcome to the program.
greet() # called again — runs the same code again
Parameters let you pass data into a function so it can work with different values each time. Return values let a function send a result back to the caller.
def greet(name):
print(f"Hello, {name}! Welcome.")
greet("Alice") # Hello, Alice! Welcome.
greet("Bob") # Hello, Bob! Welcome.
greet("Student") # Hello, Student! Welcome.
def describe_score(name, score):
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
print(f"{name} scored {score} — Grade: {grade}")
describe_score("Alice", 95) # Alice scored 95 — Grade: A
describe_score("Bob", 73) # Bob scored 73 — Grade: C
Use return to send a result back. The caller can store it in a variable or use it directly.
def add(a, b):
return a + b
def square(n):
return n * n
result = add(3, 4)
print(result) # 7
print(square(5)) # 25
print(add(10, square(3))) # 19 (10 + 9)
return statement, the function stops
immediately. Any code after return inside that function is never reached.
def average(numbers):
total = sum(numbers)
return total / len(numbers)
def highest(numbers):
return max(numbers)
scores = [88, 92, 75, 97, 61]
print(f"Average: {average(scores):.1f}") # Average: 82.6
print(f"Highest: {highest(scores)}") # Highest: 97
Real programs are built by combining many small functions, each doing one specific job. This is called decomposition. When something breaks, you only need to fix the one small function responsible — not hunt through hundreds of lines of tangled code.
# Hard to read, hard to test, hard to fix
scores = [85, 92, 78, 95, 61, 88]
total = 0
for s in scores:
total += s
avg = total / len(scores)
highest = scores[0]
for s in scores:
if s > highest:
highest = s
passing = []
for s in scores:
if s >= 70:
passing.append(s)
print(f"Avg: {avg:.1f}, High: {highest}, Passing: {len(passing)}")
def calculate_average(scores):
return sum(scores) / len(scores)
def find_highest(scores):
return max(scores)
def get_passing(scores, cutoff=70):
passing = []
for score in scores:
if score >= cutoff:
passing.append(score)
return passing
def print_summary(scores):
avg = calculate_average(scores)
highest = find_highest(scores)
passing = get_passing(scores)
print(f"Average: {avg:.1f}")
print(f"Highest: {highest}")
print(f"Passing ({len(passing)} students): {passing}")
# Now the entire program reads like plain English:
scores = [85, 92, 78, 95, 61, 88]
print_summary(scores)
# Output:
# Average: 83.2
# Highest: 95
# Passing (5 students): [85, 92, 78, 95, 88]
calculate_average,
find_highest, and get_passing can each be called independently anywhere in your
program. If you need to change how "passing" is calculated, you change it in exactly one place.
Here's a short program that combines 2D lists, loops, and functions to model a movie theater seating chart. It's a great example of how all three concepts snap together.
# A 4-row x 6-seat theater. "." = empty, "X" = taken.
def create_theater(rows, cols):
"""Return a 2D list representing an empty theater."""
theater = []
for r in range(rows):
row = []
for c in range(cols):
row.append(".")
theater.append(row)
return theater
def display_theater(theater):
"""Print the seating chart with row labels."""
print("\n " + " ".join(str(c + 1) for c in range(len(theater[0]))))
for i, row in enumerate(theater):
label = chr(65 + i) # A, B, C, D ...
print(f" {label} " + " ".join(row))
print()
def book_seat(theater, row_letter, col_number):
"""Mark a seat as taken. Returns True if successful, False if already taken."""
row = ord(row_letter.upper()) - 65 # 'A' -> 0, 'B' -> 1, etc.
col = col_number - 1 # 1-based input -> 0-based index
if theater[row][col] == "X":
return False
theater[row][col] = "X"
return True
def count_available(theater):
"""Return the number of empty seats."""
available = 0
for row in theater:
for seat in row:
if seat == ".":
available += 1
return available
# --- Run it ---
theater = create_theater(4, 6)
display_theater(theater)
# 1 2 3 4 5 6
# A . . . . . .
# B . . . . . .
# C . . . . . .
# D . . . . . .
book_seat(theater, "B", 3)
book_seat(theater, "B", 4)
book_seat(theater, "C", 3)
display_theater(theater)
# 1 2 3 4 5 6
# A . . . . . .
# B . . X X . .
# C . . X . . .
# D . . . . . .
print(f"Seats available: {count_available(theater)}") # Seats available: 21
already_booked = book_seat(theater, "B", 3)
print(f"Could book B3? {already_booked}") # Could book B3? False