Data Structures
Lists, dictionaries, tuples, and sets — the containers that organize your data, with list comprehensions and real-world examples for each.
The contact list problem
Your phone has a contact list. For each person, you store their name, phone number, email, and maybe a birthday. Now imagine trying to manage 500 contacts using nothing but individual variables:
contact1_name = "Alice"
contact1_phone = "555-0101"
contact1_email = "[email protected]"
contact2_name = "Bob"
contact2_phone = "555-0102"
# ... 1,498 more lines
This is insanity. You cannot loop through it. You cannot search it. You cannot sort it. You need a way to organize data — to group related items together and work with them efficiently.
That is what data structures do. They are containers designed for different kinds of data. Choosing the right container is like choosing the right storage for your kitchen: silverware goes in a drawer (ordered slots), spices go on a labeled rack (named slots), and a fruit bowl holds unique items you grab without caring about order.
Python gives you four core data structures, each designed for a different job.
✗ Without AI
- ✗Hundreds of individual variables
- ✗Cannot loop through data
- ✗Cannot search efficiently
- ✗Cannot sort or filter
- ✗Code breaks when data grows
✓ With AI
- ✓One variable holds all the data
- ✓Loop through with a single for loop
- ✓Search in one line
- ✓Sort and filter built in
- ✓Scales to millions of items
Lists — ordered, changeable collections
A list is an ordered sequence of items. Think of it as a numbered shopping list: item 1, item 2, item 3. You can add items, remove items, rearrange them, and access any item by its position.
# Creating lists
fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4, 5]
mixed = ["hello", 42, True, 3.14] # Lists can hold any type
empty = []
# Accessing items (index starts at 0!)
print(fruits[0]) # "apple" (first item)
print(fruits[1]) # "banana" (second item)
print(fruits[-1]) # "cherry" (last item)
Essential list operations
fruits = ["apple", "banana", "cherry"]
# Adding items
fruits.append("date") # Add to end: ["apple", "banana", "cherry", "date"]
fruits.insert(1, "blueberry") # Insert at index 1
# Removing items
fruits.remove("banana") # Remove by value
last = fruits.pop() # Remove and return last item
del fruits[0] # Remove by index
# Useful operations
print(len(fruits)) # Number of items
print("apple" in fruits) # True/False — is it in the list?
fruits.sort() # Sort in place (alphabetical)
fruits.reverse() # Reverse in place
Slicing — grabbing a piece of a list
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(numbers[2:5]) # [2, 3, 4] — index 2 up to (not including) 5
print(numbers[:3]) # [0, 1, 2] — first 3 items
print(numbers[7:]) # [7, 8, 9] — from index 7 to end
print(numbers[::2]) # [0, 2, 4, 6, 8] — every 2nd item
List Operations
25 XPDictionaries — labeled containers
A dictionary stores data as key-value pairs. Think of it as an actual dictionary: you look up a word (key) to find its definition (value). Or think of it like a contact card: the labels (Name, Phone, Email) are keys, and the actual data is the values.
# Creating a dictionary
person = {
"name": "Alice",
"age": 30,
"city": "London",
"is_student": False
}
# Accessing values by key
print(person["name"]) # "Alice"
print(person.get("age")) # 30
print(person.get("email", "N/A")) # "N/A" (default if key missing)
# Adding/updating
person["email"] = "[email protected]" # Add new key
person["age"] = 31 # Update existing key
# Removing
del person["is_student"]
When to use a list vs. a dictionary
| Use a list when... | Use a dictionary when... |
|---|---|
| Order matters | You need named access |
| Items are similar (all scores, all names) | Items have different meanings (name, age, email) |
| You access by position | You access by label/key |
Example: [85, 92, 78, 95] | Example: {"math": 85, "english": 92} |
Looping through dictionaries
person = {"name": "Alice", "age": 30, "city": "London"}
# Loop through keys
for key in person:
print(key) # name, age, city
# Loop through values
for value in person.values():
print(value) # Alice, 30, London
# Loop through both
for key, value in person.items():
print(f"{key}: {value}") # name: Alice, age: 30, city: London
There Are No Dumb Questions
"What happens if I access a key that does not exist?"
person["email"]raises aKeyErrorand crashes. Useperson.get("email")instead — it returnsNoneif the key is missing, or a default value you specify:person.get("email", "unknown"). Professional Python code almost always uses.get()for safety."Can dictionary keys be anything?"
Keys must be immutable (unchangeable) — strings, numbers, tuples, or booleans. You cannot use a list as a key because lists can change. In practice, 99% of dictionary keys are strings.
Build a Contact Book
25 XPTuples — immutable sequences
A tuple is like a list that cannot be changed after creation. Think of it as a sealed envelope — once you put data in, you cannot modify it.
# Creating tuples
coordinates = (40.7128, -74.0060) # New York City lat/long
rgb_red = (255, 0, 0)
single = (42,) # Trailing comma for single-item tuple
# Accessing (same as lists)
print(coordinates[0]) # 40.7128
print(coordinates[1]) # -74.0060
# Unpacking
lat, lon = coordinates
print(f"Latitude: {lat}, Longitude: {lon}")
# This will CRASH:
# coordinates[0] = 41.0 # TypeError: tuples do not support assignment
When to use tuples: when data should not change — coordinates, RGB colors, database rows, function return values.
Sets — unique items only
A set is an unordered collection with no duplicates. Think of it as a guest list for a party — each person can only appear once, and there is no assigned seating order.
# Creating sets
colors = {"red", "blue", "green"}
numbers = {1, 2, 3, 2, 1} # Duplicates removed → {1, 2, 3}
# From a list (removes duplicates)
emails = ["[email protected]", "[email protected]", "[email protected]"]
unique_emails = set(emails) # {"[email protected]", "[email protected]"}
# Set operations
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a & b) # {3, 4} — intersection (in both)
print(a | b) # {1, 2, 3, 4, 5, 6} — union (in either)
print(a - b) # {1, 2} — difference (in a but not b)
✗ Without AI
- ✗Ordered — items stay in position
- ✗Allows duplicates
- ✗Access by index: list[0]
- ✗Use for sequences
✓ With AI
- ✓Unordered — no guaranteed position
- ✓No duplicates ever
- ✓Cannot access by index
- ✓Use for membership testing
List comprehensions — Python's secret weapon
A list comprehension creates a new list by transforming or filtering an existing one — in a single line. It is one of the features that makes Python code so compact and readable.
# Traditional way
squares = []
for x in range(10):
squares.append(x ** 2)
# List comprehension — same result, one line
squares = [x ** 2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# With a filter
even_squares = [x ** 2 for x in range(10) if x % 2 == 0]
# [0, 4, 16, 36, 64]
# Transform strings
names = ["alice", "bob", "charlie"]
capitalized = [name.capitalize() for name in names]
# ["Alice", "Bob", "Charlie"]
The pattern: [expression for item in iterable if condition]
There Are No Dumb Questions
"When should I use a list comprehension vs. a regular loop?"
Use a comprehension when you are building a new list by transforming or filtering items. Use a regular loop when you are doing something complex (multiple operations, printing, calling functions with side effects). If the comprehension gets longer than about 80 characters, switch to a loop — readability counts.
"Can I make a dictionary comprehension?"
Yes:
{key: value for item in iterable}. Example:{name: len(name) for name in names}gives{"alice": 5, "bob": 3, "charlie": 7}. Same pattern, curly braces instead of square brackets.
Data Structure Choice
50 XPPutting it together — a grade book
students = [
{"name": "Alice", "scores": [85, 92, 78, 95]},
{"name": "Bob", "scores": [72, 68, 81, 77]},
{"name": "Charlie", "scores": [91, 95, 88, 93]},
]
for student in students:
name = student["name"]
scores = student["scores"]
avg = sum(scores) / len(scores)
highest = max(scores)
print(f"{name}: avg={avg:.1f}, highest={highest}")
This uses a list of dictionaries, each containing a list. Real-world data is almost always nested structures like this.
Key takeaways
- Lists are ordered, changeable sequences — use for collections where order matters:
[1, 2, 3] - Dictionaries are key-value pairs — use for labeled data:
{"name": "Alice", "age": 30} - Tuples are immutable sequences — use for data that should not change:
(40.71, -74.00) - Sets are unordered collections with no duplicates — use for membership and uniqueness:
{1, 2, 3} - Indexing starts at 0 — the first item is
list[0], notlist[1] - Use
.get()for dictionary access — avoids crashes when a key is missing - List comprehensions create lists in one line:
[x * 2 for x in range(10)] - Real-world data is nested — lists of dictionaries, dictionaries of lists — get comfortable with it
Knowledge Check
1.What does `[1, 2, 3, 2, 1]` become when converted to a set with `set([1, 2, 3, 2, 1])`?
2.Given `d = {"name": "Alice", "age": 30}`, what does `d.get("email", "N/A")` return?
3.What does `[x ** 2 for x in range(5)]` produce?
4.Which data structure is immutable (cannot be changed after creation)?