What’s one thing you learned? What’s still confusing?
List Comprehensions: Python's Superpower
Write concise, fast list/dict/set transformations in one readable line.
Mini-Project: Grade Statistics
Compute a class report (mean, min, max, pass rate, top scorers) using list comprehensions.
Dictionaries & Hash Tables: O(1) Lookup Explained
Python dicts: hash mechanics, collision resolution, all dict methods, and comprehensions.
Interactive Labs for This Track
Loop Visualizer
You're a factory robot repeating the same task on an assembly line — watch how loops automate repetitive work
List Slicing
You have a playlist of 50 songs — grab just tracks 10 through 20 with a single slice expression
Sorting Algorithms
You're organizing a library of 10,000 books — which sorting method is fastest?
Ask questions, share insights
This is the cheat sheet for this whole lesson. Bookmark it. Every section below zooms into one row of this table.
A list is Python's most versatile data structure. It holds an ordered collection of items that you can change (add, remove, reorder) at any time.
# Creating lists
fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4, 5]
mixed = [42, "hello", True, 3.14, None] # lists can mix types
empty = [] # empty list
print(fruits) # ['apple', 'banana', 'cherry']
print(len(fruits)) # 3Try it! Open the Python REPL and create your own shopping list:my_list = ["milk", "eggs", "bread"]. Then trymy_list.append("butter")and print it.
colors = ["red", "green", "blue", "yellow", "purple"]
# Positive indexing (from the start)
print(colors[0]) # "red" (first item)
print(colors[1]) # "green" (second item)
print(colors[4]) # "purple" (fifth item, index 4)
# Negative indexing (from the end)
print(colors[-1]) # "purple" (last item)
print(colors[-2]) # "yellow" (second to last)
# Modify an item
colors[0] = "crimson"
print(colors) # ['crimson', 'green', 'blue', 'yellow', 'purple']HitIndexError,KeyError, orTypeError: list indices must be integers? Off-by-one on indexes, accessing a missing dict key, or using a string when an int is needed — these are the three indexing crashes. See the error decoder for fixes.
list[start:stop:step]. Like range(), the stop value is excluded.nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(nums[2:5]) # [2, 3, 4] (index 2 up to but not 5)
print(nums[:3]) # [0, 1, 2] (from start to index 3)
print(nums[7:]) # [7, 8, 9] (from index 7 to end)
print(nums[::2]) # [0, 2, 4, 6, 8] (every other item)
print(nums[::-1]) # [9, 8, 7, ..., 0] (reversed!)
print(nums[1:8:3]) # [1, 4, 7] (start=1, stop=8, step=3)What does [1, 2, 3][1:] return?
[1:] means "from index 1 to the end." Index 1 is the second item (which is 2), so you get [2, 3]. Remember, Python lists are zero-indexed.heroes = ["Iron Man", "Thor", "Hulk"]
# Add items
heroes.append("Black Widow") # add to end
heroes.insert(1, "Captain America") # insert at index 1
print(heroes)
# ['Iron Man', 'Captain America', 'Thor', 'Hulk', 'Black Widow']
# Remove items
heroes.remove("Hulk") # remove by value
popped = heroes.pop() # remove and return last item
print(popped) # "Black Widow"
del heroes[0] # remove by index
# Search
print("Thor" in heroes) # True
print(heroes.index("Thor")) # 1
# Sort
scores = [85, 92, 78, 95, 88]
scores.sort() # sort in place (ascending)
print(scores) # [78, 85, 88, 92, 95]
scores.sort(reverse=True) # descending
print(scores) # [95, 92, 88, 85, 78]
# Copy
original = [1, 2, 3]
copy = original.copy() # creates a new list
copy.append(4)
print(original) # [1, 2, 3] (not affected)
print(copy) # [1, 2, 3, 4]What gets printed? nums = [1, 2, 3] result = nums.append(4) print(result)
.append() MUTATES the list in place and returns None — it doesn't give you back the modified list. The list nums is now [1, 2, 3, 4], but result is None. This trips up beginners constantly: you can't chain list methods (nums.append(4).append(5) fails) and you can't assign the result. The rule of thumb: methods that change the object usually return None (.sort(), .reverse(), .append()); methods that return a new object usually leave the original alone (sorted(), reversed(), s.upper()).b = a, you do NOT make a copy — you make a second name for the same list.# Creating tuples
coordinates = (10, 20)
rgb_red = (255, 0, 0)
single = (42,) # note the trailing comma for single-item tuples
# Accessing works the same as lists
print(coordinates[0]) # 10
print(rgb_red[-1]) # 0
# But you CANNOT modify them
# coordinates[0] = 99 # TypeError: 'tuple' object does not support item assignment# Tuples are perfect for returning multiple values from a function
def get_min_max(numbers):
return (min(numbers), max(numbers))
result = get_min_max([3, 1, 4, 1, 5, 9])
print(result) # (1, 9)
# Tuple unpacking -- assign each value to a separate variable
low, high = get_min_max([3, 1, 4, 1, 5, 9])
print(f"Min: {low}, Max: {high}") # Min: 1, Max: 9You run `point = (3, 4)` then `point[0] = 99`. What happens?
# Creating a dictionary
student = {
"name": "Alex",
"age": 16,
"grade": "11th",
"gpa": 3.85,
"is_honor_roll": True
}
# Accessing values by key
print(student["name"]) # "Alex"
print(student["gpa"]) # 3.85
# Using .get() -- returns None instead of error for missing keys
print(student.get("email")) # None
print(student.get("email", "N/A")) # "N/A" (default value)
# Adding and modifying
student["email"] = "alex@school.edu" # add new key
student["gpa"] = 3.90 # update existing key
# Removing
del student["is_honor_roll"]
removed_value = student.pop("email") # remove and return value
print(student)model_config = {
"name": "GPT-4",
"parameters": 1_700_000_000_000,
"context_length": 128_000,
"training_data_cutoff": "2023-04",
}
# Loop through keys
for key in model_config:
print(key)
# Loop through values
for value in model_config.values():
print(value)
# Loop through both (most common)
for key, value in model_config.items():
print(f"{key}: {value}")Dictionaries can contain other dictionaries, creating complex data structures:
# This is exactly what an API response looks like
api_response = {
"model": "claude-3.5-sonnet",
"usage": {
"input_tokens": 150,
"output_tokens": 500,
"total_tokens": 650,
},
"choices": [
{"text": "Hello! How can I help?", "finish_reason": "stop"}
],
}
# Access nested values
print(api_response["usage"]["total_tokens"]) # 650
print(api_response["choices"][0]["text"]) # "Hello! How can I help?"What happens? user = {"name": "Alex"} print(user["age"])
None like some other languages do. This is intentional: it forces you to acknowledge missing data instead of silently propagating bad values. For safe access, use .get(): user.get("age") returns None if missing, and user.get("age", 0) returns 0. This is one of the most common production crashes when parsing JSON from APIs — always assume keys might be missing.Which of these can be used as a dict KEY?
# Creating sets
fruits = {"apple", "banana", "cherry"}
numbers = {1, 2, 3, 2, 1} # duplicates are automatically removed
print(numbers) # {1, 2, 3}
# From a list (great for removing duplicates!)
words = ["the", "cat", "sat", "on", "the", "mat", "the"]
unique_words = set(words)
print(unique_words) # {'cat', 'mat', 'on', 'sat', 'the'}
print(f"Vocabulary size: {len(unique_words)}") # 5Sets support mathematical operations like union, intersection, and difference:
python_devs = {"Alice", "Bob", "Charlie", "Diana"}
js_devs = {"Bob", "Diana", "Eve", "Frank"}
# Union -- everyone who knows at least one language
print(python_devs | js_devs)
# {'Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank'}
# Intersection -- people who know both
print(python_devs & js_devs)
# {'Bob', 'Diana'}
# Difference -- Python devs who do NOT know JS
print(python_devs - js_devs)
# {'Alice', 'Charlie'}
# Symmetric difference -- people who know exactly one
print(python_devs ^ js_devs)
# {'Alice', 'Charlie', 'Eve', 'Frank'}Sets are great for membership testing (is this item in the set?) because lookups are nearly instant, no matter how large the set is:
# Checking membership -- O(1) time, very fast
stop_words = {"the", "a", "an", "is", "are", "was", "were", "in", "on", "at"}
word = "the"
print(word in stop_words) # True -- instant lookupWhat does `set([1, 2, 2, 3, 3, 3, 4])` produce?
A list comprehension lets you create a new list by transforming or filtering an existing one, all in a single line:
# Without comprehension (4 lines)
squares = []
for x in range(10):
squares.append(x ** 2)
# With comprehension (1 line -- same result)
squares = [x ** 2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81][expression for item in iterable]# Only even squares
even_squares = [x ** 2 for x in range(10) if x % 2 == 0]
print(even_squares) # [0, 4, 16, 36, 64]
# Filter words longer than 3 characters
words = ["I", "am", "learning", "Python", "for", "AI"]
long_words = [w for w in words if len(w) > 3]
print(long_words) # ['learning', 'Python']
# Convert temperatures
fahrenheit = [32, 68, 77, 95, 212]
celsius = [(f - 32) * 5/9 for f in fahrenheit]
print([f"{c:.1f}" for c in celsius]) # ['0.0', '20.0', '25.0', '35.0', '100.0']# Dictionary comprehension
word_lengths = {word: len(word) for word in ["Python", "AI", "ML", "Data"]}
print(word_lengths) # {'Python': 6, 'AI': 2, 'ML': 2, 'Data': 4}
# Set comprehension
first_letters = {word[0].lower() for word in ["Python", "programming", "AI", "awesome"]}
print(first_letters) # {'p', 'a'}Tests · Run to see averages and grades. Then try each challenge!
Here is a quick cheat sheet:
| Structure | Ordered? | Mutable? | Duplicates? | Use When... |
|---|---|---|---|---|
List [] | Yes | Yes | Yes | You need an ordered collection you can modify (most common) |
Tuple () | Yes | No | Yes | Data should not change (coordinates, return values, dict keys) |
Dict {} | Yes* | Yes | Keys: No, Values: Yes | You need to look up values by a meaningful name/key |
Set {} |
*Dicts preserve insertion order since Python 3.7.
# Training data is a list of dictionaries
training_data = [
{"text": "Great movie!", "label": "positive"},
{"text": "Terrible film.", "label": "negative"},
{"text": "Loved every minute.", "label": "positive"},
]
# Vocabulary is a set (unique words only)
vocabulary = set()
for sample in training_data:
for word in sample["text"].lower().split():
vocabulary.add(word)
print(f"Vocabulary: {vocabulary}")
print(f"Vocab size: {len(vocabulary)}")
# Model config is a dictionary
config = {
"model_type": "transformer",
"hidden_size": 768,
"num_layers": 12,
"learning_rate": 3e-5,
"batch_size": 32,
}
# Predictions are a list of tuples (text, label, confidence)
predictions = [
("Great movie!", "positive", 0.95),
("Terrible film.", "negative", 0.88),
]
for text, label, confidence in predictions:
print(f"'{text}' -> {label} ({confidence:.0%})")list[0]), slice with list[start:stop], and use methods like .append(), .sort(), .pop(). They are the workhorse of Python(x, y) or function return values. Unpack with a, b = my_tupledict["name"]), iterate with .items(). They power JSON and every API you will ever use|), intersection (&), and difference (-)[expression for item in iterable if condition] is cleaner and faster than a manual loopWhat does [10, 20, 30, 40, 50][1:4] return?
This program stores a 2D point as a tuple, then tries to move it by reassigning its x-coordinate. It crashes with a TypeError. Fix it without changing how the point is stored.
Before: (3, 4) After: (5, 4)
Build a tiny inventory system. Use the RIGHT data structure for each piece: 1. `unique_skus` — a SET of all SKUs ever seen (no duplicates). 2. `quantities` — a DICT mapping SKU to current quantity. 3. `log` — a LIST of every transaction as tuples (sku, change). Write a function `record(sku, change)` that: - adds `sku` to `unique_skus` - updates `quantities[sku]` by `change` (positive = stock in, negative = stock out) - appends `(sku, change)` to `log` Then print all three after a few transactions.
unique SKUs: {'A1', 'B2'}
quantities: {'A1': 8, 'B2': 3}
log: [('A1', 10), ('B2', 5), ('A1', -2), ('B2', -2)]unique_skus = set()
quantities = {}
log = []
def record(sku, change):
# TODO: update unique_skus, quantities, and log
pass
record("A1", 10)
record("B2", 5)
record("A1", -2)
record("B2", -2)
print("unique SKUs:", unique_skus)
print("quantities:", quantities)
print("log:", log)# Fast membership check — always prefer set over list for this
words_to_avoid = {"spam", "click here", "free money"} # set
if any(word in email.lower() for word in words_to_avoid):
print("Looks like spam!")| No |
| Yes |
| No |
| You need unique items, fast membership checks, or set math |