Chapter 11: Tuples and Sets
You know lists. Lists are ordered, changeable, and incredibly useful. But Python has two more collection types that solve different problems — and knowing which one to reach for is a mark of a thoughtful programmer.
Tuples are like lists that you can never change. Once you create a tuple, it's locked. That sounds like a limitation, but it's actually a feature — you use it to signal "this data is fixed and shouldn't change."
Sets are unordered collections with no duplicates. They're designed for membership testing and set math — finding what's common, what's different, and what's unique.
Both are lightweight, fast, and used constantly in real Python code. By the end of this chapter you'll know what they are, when to use them, and how to choose between a tuple, a set, and a list.
Tuples — Immutable Sequences
Creating a tuple
Use parentheses with comma-separated values:
point = (3, 7)
colors = ("red", "green", "blue")
person = ("Alice", 30, "London")
empty = ()
You can also create a tuple without parentheses — the comma is what makes a tuple, not the parentheses:
point = 3, 7
print(point) # (3, 7)
print(type(point)) # <class 'tuple'>
One-item tuple: You need a trailing comma, otherwise Python just sees parentheses around a value:
not_a_tuple = (42)
is_a_tuple = (42,)
print(type(not_a_tuple)) # <class 'int'>
print(type(is_a_tuple)) # <class 'tuple'>
Indexing and slicing — same as lists
colors = ("red", "green", "blue")
print(colors[0]) # red
print(colors[-1]) # blue
print(colors[1:]) # ('green', 'blue')
print(len(colors)) # 3
Tuples are immutable
colors = ("red", "green", "blue")
colors[0] = "yellow" # TypeError: 'tuple' object does not support item assignment
You can't change a tuple after creation. Period. This is intentional — it's the whole point.
Looping over a tuple
rgb = (255, 128, 0)
for value in rgb:
print(value)
Output:
255
128
0
Everything you can do with a list in a for loop works with a tuple.
Tuple methods
Tuples have only two methods (because you can't change them, most list methods don't apply):
numbers = (1, 2, 3, 2, 4, 2, 5)
print(numbers.count(2)) # 3 — how many times 2 appears
print(numbers.index(4)) # 4 — position of first 4
Tuple packing and unpacking
Packing is putting multiple values into a tuple:
coordinates = 40.7128, -74.0060 # New York City lat/long
print(coordinates) # (40.7128, -74.006)
Unpacking is the reverse — splitting a tuple into individual variables:
x, y = (3, 7)
print(x) # 3
print(y) # 7
name, age, city = ("Alice", 30, "London")
print(f"{name} is {age} and lives in {city}.")
# Alice is 30 and lives in London.
Unpacking is one of Python's most elegant features. You've already seen it when a function returns multiple values:
def get_dimensions():
return 1920, 1080 # returns a tuple
width, height = get_dimensions()
print(f"{width} x {height}") # 1920 x 1080
Swap two variables using tuple unpacking — no temporary variable needed:
a = 10
b = 20
a, b = b, a # swap!
print(a) # 20
print(b) # 10
In most languages, swapping requires a temporary variable. In Python, tuple unpacking makes it a one-liner. You'll use this more than you'd expect.
Extended unpacking with *:
first, *middle, last = (1, 2, 3, 4, 5)
print(first) # 1
print(middle) # [2, 3, 4]
print(last) # 5
The * soaks up everything that doesn't fit into the named variables. The middle variable becomes a list.
head, *tail = ("apple", "banana", "cherry", "date")
print(head) # apple
print(tail) # ['banana', 'cherry', 'date']
Your turn: Create a tuple with five numbers. Unpack the first and last into named variables using extended unpacking. Print all three parts.
When to Use a Tuple Instead of a List
This is the question beginners always ask. Here's the honest answer:
Use a tuple when the data is fixed and shouldn't change. Use a list when the data might grow, shrink, or be updated.
| Situation | Use |
|---|---|
RGB color value: (255, 128, 0) |
Tuple — it's a fixed set of three values |
GPS coordinates: (40.7128, -74.0060) |
Tuple — lat/long don't change |
| Days of the week | Tuple — there are always seven |
| A shopping cart | List — items get added and removed |
| Student grades | List — new grades get added |
| Returning multiple values from a function | Tuple |
| Dictionary keys | Tuple (lists can't be dict keys; tuples can) |
Tuples as dictionary keys is a real practical reason to use them:
# Map grid coordinates to a value
grid = {
(0, 0): "start",
(1, 0): "wall",
(1, 1): "open",
(2, 2): "goal",
}
print(grid[(0, 0)]) # start
print(grid[(2, 2)]) # goal
A list cannot be used as a dictionary key — only immutable types can. Tuples are immutable, so they can.
Tuples are slightly faster and use less memory than lists. For large amounts of fixed data, this matters.
Your turn: A record store has albums stored as (title, artist, year) tuples in a list. Create five album tuples, put them in a list, and loop through the list printing each album's details in a formatted string.
Sets — Unique, Unordered Collections
A set stores unique values with no defined order. If you add a duplicate, it's silently ignored. There's no first item or last item — sets don't track position.
Creating a set
Use curly braces {}:
fruits = {"apple", "banana", "cherry"}
print(fruits) # {'apple', 'cherry', 'banana'} (order not guaranteed)
Or use set() to create a set from another iterable:
numbers = set([1, 2, 3, 2, 1, 4])
print(numbers) # {1, 2, 3, 4} (duplicates removed)
Important: An empty {} creates a dict, not a set. Use set() for an empty set:
empty_dict = {}
empty_set = set()
Duplicates are automatically removed
This is the most common reason to use a set — you have a list with duplicates and you want only the unique values.
votes = ["Alice", "Bob", "Alice", "Carlos", "Bob", "Alice", "Diana"]
unique_voters = set(votes)
print(unique_voters) # {'Alice', 'Bob', 'Carlos', 'Diana'}
print(len(unique_voters)) # 4
Adding and removing
fruits = {"apple", "banana", "cherry"}
fruits.add("date")
print(fruits) # {'apple', 'banana', 'cherry', 'date'}
fruits.add("apple") # duplicate — silently ignored
print(fruits) # still 4 items
fruits.remove("banana") # raises KeyError if not found
print(fruits)
fruits.discard("grape") # safe remove — no error if not found
print(fruits)
Use discard() when you're not sure if the item is there. Use remove() when it must be there (and you want an error if it's not).
Membership testing — this is where sets shine
allowed_extensions = {".jpg", ".png", ".gif", ".webp"}
filename = "photo.jpg"
ext = filename[filename.rfind("."):]
if ext in allowed_extensions:
print("File type accepted.")
else:
print("File type not allowed.")
Checking in for a set is dramatically faster than checking in for a list — especially for large collections. A list scans every element one by one. A set uses a hash table and finds the answer almost instantly regardless of size.
For small collections the difference doesn't matter. For thousands or millions of items, it's the difference between fast and unusably slow.
# Use a set for fast lookups
blocked_users = {"spammer42", "troll99", "badactor"}
username = input("Enter username: ")
if username in blocked_users:
print("Access denied.")
else:
print(f"Welcome, {username}!")
Your turn: You have a list of 10 words, some repeated. Using a set, find how many unique words there are. Then check if the word "python" is in the set.
Set Operations — The Real Power
Sets support mathematical set operations: union, intersection, difference, and symmetric difference. These are incredibly useful for comparing collections.
Union — everything in either set
python_devs = {"Alice", "Bob", "Carlos", "Diana"}
java_devs = {"Carlos", "Diana", "Eve", "Frank"}
all_devs = python_devs | java_devs
# or: python_devs.union(java_devs)
print(all_devs)
# {'Alice', 'Bob', 'Carlos', 'Diana', 'Eve', 'Frank'}
Intersection — only what's in both
both = python_devs & java_devs
# or: python_devs.intersection(java_devs)
print(both) # {'Carlos', 'Diana'}
Difference — in the first but not the second
only_python = python_devs - java_devs
# or: python_devs.difference(java_devs)
print(only_python) # {'Alice', 'Bob'}
only_java = java_devs - python_devs
print(only_java) # {'Eve', 'Frank'}
Symmetric difference — in one or the other, but not both
exclusive = python_devs ^ java_devs
# or: python_devs.symmetric_difference(java_devs)
print(exclusive) # {'Alice', 'Bob', 'Eve', 'Frank'}
Checking subset and superset
admins = {"Alice", "Bob"}
all_users = {"Alice", "Bob", "Carlos", "Diana"}
print(admins.issubset(all_users)) # True — all admins are users
print(all_users.issuperset(admins)) # True — all_users contains all admins
print(admins.isdisjoint({"Carlos"})) # True — no overlap
A real example: comparing survey responses
survey_week1 = {"Alice", "Bob", "Carlos", "Diana", "Eve"}
survey_week2 = {"Bob", "Diana", "Frank", "Grace", "Alice"}
returned = survey_week1 & survey_week2
new_this_week = survey_week2 - survey_week1
dropped_off = survey_week1 - survey_week2
total_reached = survey_week1 | survey_week2
print(f"Responded both weeks: {returned}")
print(f"New this week: {new_this_week}")
print(f"Didn't return: {dropped_off}")
print(f"Total unique people: {len(total_reached)}")
Output (order may vary):
Responded both weeks: {'Alice', 'Bob', 'Diana'}
New this week: {'Frank', 'Grace'}
Didn't return: {'Carlos', 'Eve'}
Total unique people: 7
Four lines of set operations replace what would otherwise take dozens of lines of loops and conditionals.
Your turn: You have two lists of student names — one for Math class and one for Science class. Using sets, find: students in both classes, students only in Math, students only in Science, and total unique students across both classes.
Updating Sets In Place
The set operators (|, &, -, ^) return new sets. The update methods modify the set in place:
tags = {"python", "programming"}
tags.update({"web", "api"}) # add multiple items (union in place)
tags.intersection_update({"python", "web", "data"}) # keep only common items
tags.difference_update({"web"}) # remove items in another set
print(tags) # {'python'}
frozenset — An Immutable Set
Just as tuples are immutable lists, frozenset is an immutable set. You can't add or remove items after creation. Why would you want this?
- A
frozensetcan be used as a dictionary key (a regular set cannot). - A
frozensetcan be an element inside another set.
vowels = frozenset("aeiou")
print(vowels) # frozenset({'a', 'e', 'i', 'o', 'u'})
print("a" in vowels) # True
# Can be used as a dict key
valid_types = {
frozenset({"jpg", "png"}): "image",
frozenset({"mp3", "wav"}): "audio",
}
You'll use frozenset rarely, but it's good to know it exists when you need a hashable, immutable set.
Choosing Between List, Tuple, and Set
| Need | Use |
|---|---|
| Ordered, changeable collection | list |
| Ordered, fixed collection | tuple |
| Unique values, no order needed | set |
| Fast membership testing | set |
| Dictionary key | tuple or frozenset |
| Multiple return values from a function | tuple |
| Remove duplicates from a list | set(list) |
| Set math (union, intersection, etc.) | set |
The most common pattern you'll use from this chapter:
# Remove duplicates from a list while preserving order
items = ["banana", "apple", "banana", "cherry", "apple"]
seen = set()
unique = []
for item in items:
if item not in seen:
unique.append(item)
seen.add(item)
print(unique) # ['banana', 'apple', 'cherry']
Or the quick version when order doesn't matter:
items = ["banana", "apple", "banana", "cherry", "apple"]
unique = list(set(items))
print(unique) # ['apple', 'banana', 'cherry'] (order not guaranteed)
Putting It All Together: Analyzing Word Frequency
def analyze_words(text):
"""Analyze word statistics in a block of text."""
# Clean and split into words
words = text.lower().split()
cleaned = [w.strip(".,!?;:\"'") for w in words]
total_words = len(cleaned)
unique_words = set(cleaned)
unique_count = len(unique_words)
most_common = max(unique_words, key=cleaned.count)
# Common words to ignore
stopwords = frozenset({"the", "a", "an", "is", "in", "it",
"to", "and", "of", "for", "on", "are"})
content_words = unique_words - stopwords
print(f"Total words: {total_words}")
print(f"Unique words: {unique_count}")
print(f"Content words: {len(content_words)}")
print(f"Most common word: '{most_common}' ({cleaned.count(most_common)}x)")
print(f"Vocabulary richness: {unique_count / total_words:.1%}")
text = """
Python is a programming language. Python is easy to learn and Python is
powerful. Many programmers use Python for data science, web development,
and automation. Python is growing in popularity every year.
"""
analyze_words(text)
Output:
Total words: 31
Unique words: 19
Content words: 14
Most common word: 'python' (5x)
Vocabulary richness: 61.3%
Strings, lists, sets, and a frozenset — all working together in thirty lines.
What You Learned in This Chapter
- A tuple is an immutable, ordered sequence. Created with
()or just commas. - Tuples support indexing, slicing,
len(),count(), andindex(). - Tuple unpacking assigns multiple variables at once:
a, b = (1, 2). - Extended unpacking:
first, *rest = tuplecollects the remainder into a list. - Swapping variables:
a, b = b, a— no temporary variable needed. - Use tuples for fixed data, multiple return values, and dictionary keys.
- A set is an unordered collection of unique values. Created with
{}orset(). - Sets automatically remove duplicates.
add()adds one item.discard()removes without error.remove()raisesKeyError.- Membership testing with
inis much faster on sets than lists. - Set operations:
|union,&intersection,-difference,^symmetric difference. frozensetis an immutable set — usable as a dict key or set element.- Remove list duplicates:
list(set(my_list))(order not preserved).
What's Next?
Tuples and sets handle ordered-but-fixed and unordered-unique data. But the most versatile collection in Python — the one you'll use in almost every real program — is the dictionary.
In Chapter 12 you'll learn dictionaries: key-value pairs that let you look up any piece of data by a meaningful name rather than a numeric index. Think of a dictionary as a real dictionary — you look up a word (the key) and get its definition (the value). Incredibly powerful, incredibly common.
Your turn: Write a program that reads a sentence from the user, finds the unique characters (excluding spaces), and prints them in sorted order. Then print how many unique characters there are. Bonus: also print which letters are vowels using set intersection.