Week 3 Code Examples

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.

How to use this page: Copy any example into a .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.

1. Lists — Storing Multiple Items in One Variable

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.

Key concept: Lists let you work with a whole collection of values using a single variable name. This is one of the most important ideas in programming.

Creating a list

# 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

Accessing items by index

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
Watch out: Indexes start at 0, not 1. The first item is list[0], not list[1]. Forgetting this is one of the most common beginner bugs.

2. List Operations — Append, Remove, Indexing & Slicing

Lists aren't fixed — you can add, remove, and reorganize items after you create them.

append() — add to the end

shopping = ["milk", "eggs"] shopping.append("bread") shopping.append("butter") print(shopping) # ['milk', 'eggs', 'bread', 'butter'] print(len(shopping)) # 4

remove() — delete by value

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 — grab a portion of a list

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

Other useful list operations

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

List processing in action — why this matters

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.
The big idea: A list + a loop replaces any number of separate variables. You write the logic once and it works on 8 items or 800 items without any changes.

3. For Loops — Iterating Over Lists

A for loop automatically goes through every item in a list one at a time. This is how you automate repetitive tasks.

Key concept: Instead of writing the same code 10 times for 10 items, you write it once inside a for loop. The loop runs it once for each item automatically.

Basic for loop

fruits = ["apple", "banana", "cherry"] for fruit in fruits: print(f"I like {fruit}") # Output: # I like apple # I like banana # I like cherry

Loop with index using enumerate()

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

range() — loop a set number of times

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)

Building a new list inside a loop

scores = [55, 72, 88, 44, 91] passing = [] for score in scores: if score >= 60: passing.append(score) print(passing) # [72, 88, 91]

4. 2D Lists — Representing Grids and Matrices

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.

Key concept: Access an item in a 2D list with grid[row][col]. Think of it as: first pick the row, then pick the column within that row.

Creating a 2D list

# 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']

Printing a 2D list with nested loops

board = [ ["X", " ", "O"], [" ", "X", " "], ["O", " ", "X"], ] for row in board: print("|" + "|".join(row) + "|") # Output: # |X| |O| # | |X| | # |O| |X|

Nested loops to visit every cell

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)

5. Functions — Defining Reusable Code Blocks

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.

Key concept — The DRY Principle: "Don't Repeat Yourself." If you find yourself writing the same logic more than once, put it in a function. Every time you call the function, it runs that code without you rewriting it.

Defining and calling a function

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

6. Parameters and Return Values

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.

Function with parameters

def greet(name): print(f"Hello, {name}! Welcome.") greet("Alice") # Hello, Alice! Welcome. greet("Bob") # Hello, Bob! Welcome. greet("Student") # Hello, Student! Welcome.

Multiple parameters

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

Return values

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)
Important: Once Python hits a return statement, the function stops immediately. Any code after return inside that function is never reached.

Returning from a list-processing function

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

7. Breaking Complex Problems into Functions

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.

Rule of thumb: If a function is getting long (more than ~15–20 lines), or if it's doing more than one distinct job, split it up.

Before decomposition — one big messy block

# 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)}")

After decomposition — clean and reusable

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]
The DRY Principle in action: Notice that 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.

8. Putting It All Together — A Complete Example

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
Notice the pattern: Each function does exactly one job — create, display, book, or count. None of them need to know how the others work. That's decomposition in action.