Python Tuples and Sets: The Complete Beginner's Guide
Lists let you edit a growing sequence. Tuples bundle fixed records you should not mutate — and can be used as dict keys when every element is hashable. Sets store unique items and give you union, intersection, and difference without nested loops.
Chapter 6 of 20 · Beginner · 35 min · Python Programming Course
In Day 5 you learned that lists are ordered, mutable sequences — the workhorse collection you reach for when data grows, shrinks, or gets rearranged. This chapter introduces two collections that solve different problems.
Tuples answer: "How do I group a fixed set of values that belong together and should never be changed?" Coordinates. RGB colours. HTTP responses. Function return values. These are records — not timelines — and Python gives them a distinct type to communicate that intent clearly.
Sets answer: "How do I store unique items and ask fast membership and overlap questions?" Deduplicate a list of emails. Find students enrolled in two courses. Check whether required permissions are present. Sets are built for exactly this.
Lists, tuples, and sets are not competing — they are complementary. By the end of this chapter your mental map of Python's core collections is complete.
What You Will Learn in This Chapter
By the end of this tutorial you will be able to:
- Create tuples, including the one-element tuple rule
- Index, slice, and unpack tuples including starred unpacking with
* - Explain why tuples are immutable and when that matters
- Use
count()andindex()on tuples - Return multiple values from functions using tuples
- Use tuples as dictionary keys and set members
- Create sets using literals and
set()— and avoid the empty dict trap - Add and remove set members with
add(),remove(),discard(), andpop() - Apply union, intersection, difference, and symmetric difference
- Check subset, superset, and disjoint relationships
- Write set comprehensions
- Avoid the five most common tuple and set mistakes
Estimated time: 35 minutes reading + 20 minutes practice
Keep a REPL or editor open — the fastest way to internalise immutability and set algebra is to type the examples and tweak them yourself.
Part 1: Tuples
What Is a Python Tuple?
A tuple is an ordered, immutable sequence of items. Like a list, items have positions starting at 0 and you can slice and iterate over them. Unlike a list, once a tuple is created you cannot add, remove, or change any of its items.
point = (10, 20)
rgb = (255, 128, 0)
empty = ()
single = (42,) # trailing comma required — explained below
print(point) # (10, 20)
print(rgb) # (255, 128, 0)
print(empty) # ()
print(single) # (42,)
Four properties to remember:
- Ordered — indexes and slices work exactly like lists and strings.
- Immutable — the tuple object does not change after creation.
- Allows duplicates —
(1, 1, 2)is a valid tuple. - Hashable — when all items are hashable, the whole tuple is hashable and can be used as a dictionary key or set member. Lists cannot.
Tuples use parentheses in documentation and style guides, but the comma is what actually builds the tuple — parentheses are often just grouping for visual clarity.
The One-Element Tuple — Why the Comma Matters
This is the one syntax rule that catches every beginner exactly once:
a = (42) # NOT a tuple — just the integer 42 with grouping parentheses
b = (42,) # tuple — the trailing comma is what makes it one
print(type(a)) # <class 'int'>
print(type(b)) # <class 'tuple'>
Parentheses mean "group this expression" everywhere in Python — so (42) is simply 42. A true single-element tuple requires the trailing comma. Without it you do not have a tuple.
Tuple Indexing and Slicing
Indexing and slicing mirror lists completely. Slicing always returns a new tuple — it never mutates the original:
coords = (12.5, -3.2, 100.0, 45.8)
print(coords[0]) # 12.5 — first item
print(coords[-1]) # 45.8 — last item
print(coords[0:2]) # (12.5, -3.2)
print(coords[::-1]) # (45.8, 100.0, -3.2, 12.5) — reversed
Attempting to modify a tuple raises an immediate TypeError:
coords = (1, 2, 3)
coords = (4, 5, 6) # fine — rebinding the name to a new tuple
# coords[0] = 9 # TypeError: 'tuple' object does not support item assignment
Mutable Objects Inside a Tuple
A tuple only guarantees that it will not change which objects it references. If one of those references is a mutable object like a list, that inner object can still be modified:
record = ("Alice", [88, 92])
record[1].append(79) # modifying the list inside the tuple
print(record) # ('Alice', [88, 92, 79])
This is valid Python and a common source of confusion. The tuple itself did not change — it still references the same list. But that list changed. If you need a truly immutable record, keep all inner structures immutable too — tuples of numbers, strings, and other tuples.
Tuple Methods: count() and index()
Tuples only expose two methods. Everything else list-style does not exist by design:
scores = (88, 92, 88, 74, 88, 92)
print(scores.count(88)) # 3 — how many times 88 appears
print(scores.index(92)) # 1 — position of first occurrence of 92
index() raises a ValueError if the item is not found. Use membership check first when the item might be absent:
needle = 100
if needle in scores:
print(scores.index(needle))
else:
print("not found")
Tuple Unpacking — Python's Most Used Assignment Trick
Unpacking assigns names to each position in a tuple in one line. This is one of the most common patterns in all of Python:
name, age, city = ("Elena", 30, "NYC")
print(name) # Elena
print(age) # 30
print(city) # NYC
The number of variables on the left must match the number of items in the tuple — or you get a ValueError.
Swapping Variables Without a Temporary
The swap pattern from Day 3 uses tuple packing on the right and unpacking on the left:
a = 10
b = 20
a, b = b, a
print(a, b) # 20 10
Python builds the tuple (b, a) first, then unpacks it into a and b. No temporary variable needed — and this is faster to write and read than the three-step approach most other languages require.
Extended Unpacking With *
When you do not know how many items you will get, use * to capture a variable number of items into a list:
first, *middle, last = (10, 20, 30, 40, 50)
print(first) # 10
print(middle) # [20, 30, 40] — a list, not a tuple
print(last) # 50
head, *tail = (1, 2, 3, 4, 5)
print(head) # 1
print(tail) # [2, 3, 4, 5]
Note that middle and tail are lists — that is how Python's starred expressions work regardless of the source type.
Ignoring Values With _
Convention: use _ for positions you do not care about:
status, _, payload = ("OK", "metadata-you-dont-need", {"id": 1})
print(status, payload) # OK {'id': 1}
_ is a real variable — it just signals to readers "this value is intentionally discarded."
Returning Multiple Values From Functions
Python does not need a special syntax to return more than one value. Commas on a return statement build a tuple automatically:
def min_max(values):
return min(values), max(values)
low, high = min_max([3, 9, 1, 7, 5])
print(low, high) # 1 9
def parse_response(raw):
return 200, "OK", {"user": "alice"}
status, message, data = parse_response("")
print(status, message) # 200 OK
This pattern is everywhere in Python — str.partition() returns a tuple, dict.items() yields key-value tuples, enumerate() yields index-value tuples. The mental model is always the same: a fixed bundle of related values handed back as one unit.
Tuples as Dictionary Keys and Set Members
Because tuples of hashable items are themselves hashable, they can be used as dictionary keys and set members. Lists cannot — they are mutable and therefore not hashable.
# Tuple as dictionary key — composite key without a full class
locations = {
(0, 0): "origin",
(1, 0): "east",
(0, 1): "north"
}
print(locations[(1, 0)]) # east
# Tuples in a set — unique coordinate pairs
visited = {(0, 0), (1, 0), (0, 1), (1, 0)}
print(len(visited)) # 3 — duplicate removed
This is one of the clearest real-world reasons tuples exist separately from lists: they give you a lightweight composite key without building a full class hierarchy.
Tuple Comparison — Lexicographic Ordering
Tuples compare element by element — first position 0, then position 1 if equal, and so on. This is called lexicographic ordering:
print((1, "z") < (2, "a")) # True — 1 < 2, stops here
print((1, "b") < (1, "c")) # True — first equal, "b" < "c"
print((2, 0) > (1, 9)) # True — 2 > 1, stops here
This behaviour is why tuples work so well as sort keys — you can sort a list of tuples and Python handles multi-field comparison automatically without extra classes or custom comparators.
When to Use a Tuple vs a List
| Reach for a tuple when | Reach for a list when |
|---|---|
| Length is fixed by meaning: `(x, y)`, `(r, g, b)`, `(status, message)` | You will append, pop, sort, or edit in place |
| You want to prevent accidental mutation | Collection grows from user input or file rows |
| You need hashability for dict keys or set membership | Items are homogeneous and changeable |
| Returning multiple values from a function | Order matters and you need to rearrange |
If you are unsure, defaulting to lists is fine. As you write larger programs, reach for tuples when immutability makes intent obvious.
Named Tuples — Readable Tuples With Field Names
When a tuple grows beyond two or three anonymous positions, named tuples add field names without building a full class:
from collections import namedtuple
Person = namedtuple("Person", ["name", "age", "city"])
p = Person("Elena", 30, "NYC")
print(p.name) # Elena
print(p.age) # 30
print(p[0]) # Elena — positional access still works
print(p) # Person(name='Elena', age=30, city='NYC')
Named tuples are still immutable — you cannot assign to p.name. They are a standard library staple you will read in older tutorials and production codebases. Modern Python also offers typing.NamedTuple and dataclasses for more complex needs — but namedtuple is the starting point.
Part 2: Sets
What Is a Python Set?
A set is an unordered collection of unique, hashable items. Sets are mutable — you can add and remove elements — but there is no indexing because sets make no positional guarantees.
tags = {"python", "tutorial", "beginner", "python"}
print(tags) # {'python', 'tutorial', 'beginner'} — duplicate removed
print(len(tags)) # 3
Sets are optimised for two questions: "Is x in this collection?" and "How does this collection overlap with another?" For large collections, set membership tests are dramatically faster than scanning a list.
Creating Sets
Non-Empty Sets — Curly Braces
colours = {"red", "green", "blue"}
numbers = {1, 2, 3, 4, 5}
mixed = {"alice", 42, True}
The Empty Set Trap — Always Use set()
This is the most important gotcha in this chapter:
empty_set = set() # correct — creates an empty set
empty_dict = {} # this is an empty DICT, not a set
print(type(empty_set)) # <class 'set'>
print(type(empty_dict)) # <class 'dict'>
{} was used for dictionary literals before Python added set literals. It remains a dict for backward compatibility. Always use set() for an empty set.
Converting Other Iterables — Deduplication
Passing any iterable to set() removes duplicates instantly:
ids = [101, 202, 101, 303, 202, 101]
unique_ids = set(ids)
print(unique_ids) # {101, 202, 303}
print(len(unique_ids)) # 3
# Unique characters in a string
print(set("hello")) # {'h', 'e', 'l', 'o'} — one 'l'
# Unique words in a sentence
sentence = "python is great and python is fast"
print(set(sentence.split()))
# {'python', 'is', 'great', 'and', 'fast'}
Note: set("hello") iterates characters, not words. Always split() first if you want unique words.
Set Membership and Iteration
in and not in work exactly as expected:
colours = {"red", "green", "blue"}
print("red" in colours) # True
print("yellow" in colours) # False
print("yellow" not in colours) # True
Iterating a set works but order is not guaranteed:
for colour in colours:
print(colour) # red, blue, green — or any order
For consistent display, wrap with sorted():
print(sorted(colours)) # ['blue', 'green', 'red']
Modifying Sets
s = {1, 2, 3}
# add() — adds a single item
s.add(4)
print(s) # {1, 2, 3, 4}
# update() — adds multiple items from any iterable
s.update([5, 6, 5]) # duplicates ignored
print(s) # {1, 2, 3, 4, 5, 6}
# remove() — removes item, raises KeyError if missing
s.remove(2)
print(s) # {1, 3, 4, 5, 6}
# discard() — removes item silently if missing
s.discard(99) # no error
print(s) # {1, 3, 4, 5, 6}
# pop() — removes and returns an arbitrary item
x = s.pop()
print(f"popped {x}, remaining {s}")
# clear() — empties the set
s.clear()
print(s) # set()
remove() vs discard(): Use remove() when the item must be present and its absence indicates a bug. Use discard() when absence is normal and you want to avoid the try/except.
pop() on sets removes an arbitrary item — not the "first" or "last" because sets have no order. Use it only when any item is acceptable to lose. For predictable removal, use remove() or discard().
Set Algebra — The Headline Feature
This is what sets are built for. Four operations cover the vast majority of real-world set problems:
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
# Union — all elements from either set
print(a | b) # {1, 2, 3, 4, 5, 6}
print(a.union(b)) # same result
# Intersection — only elements in both sets
print(a & b) # {3, 4}
print(a.intersection(b))
# Difference — in a but not in b
print(a - b) # {1, 2}
print(a.difference(b))
# Symmetric difference — in either but not both
print(a ^ b) # {1, 2, 5, 6}
print(a.symmetric_difference(b))
When to use operators vs methods: use operators (|, &, -, ^) for clean two-set expressions. Use methods (union(), intersection()) when combining more than two sets — methods accept multiple arguments:
result = a.union(b, {7, 8}, {9}) # union of four sets
In-Place Set Updates
When you want to modify the left-hand set instead of creating a new one:
pending = {1, 2, 3, 4, 5}
done = {2, 4}
pending -= done # same as pending.difference_update(done)
print(pending) # {1, 3, 5}
queue = {10, 20, 30}
queue |= {30, 40} # union update
print(queue) # {10, 20, 30, 40}
Subset, Superset, and Disjoint Relationships
admins = {"alice", "bob"}
staff = {"alice", "bob", "carol", "david"}
# Is admins a subset of staff? (all admins are staff)
print(admins <= staff) # True
print(admins.issubset(staff)) # True
# Is staff a superset of admins? (staff contains all admins)
print(staff >= admins) # True
print(staff.issuperset(admins)) # True
# Proper subset — subset but not equal
print(admins < staff) # True — admins is smaller
print(staff < staff) # False — equal sets are not proper subsets
# Disjoint — no elements in common
required = {"read", "write"}
given = {"execute", "delete"}
print(required.isdisjoint(given)) # True — no overlap
Real use case — permission validation:
required_permissions = {"read", "write", "execute"}
user_permissions = {"read", "write", "execute", "delete"}
if required_permissions.issubset(user_permissions):
print("Access granted")
else:
missing = required_permissions - user_permissions
print(f"Access denied. Missing: {missing}")
Set Comprehensions
The same idea as list comprehensions, but with curly braces and guaranteed uniqueness:
squares = {n ** 2 for n in range(1, 8)}
print(squares) # {1, 4, 9, 16, 25, 36, 49}
words = ["apple", "banana", "fig", "date", "apple", "fig"]
unique_long = {w for w in words if len(w) > 3}
print(unique_long) # {'apple', 'banana', 'date'}
Curly braces with expression for item in iterable build a set. Add : and you have a dict comprehension. If the distinction is unclear, use set(... for ...) with a generator expression instead.
Frozenset — Immutable Sets
When you need set-like uniqueness but the collection itself must be immutable — for use as a dict key or inside another set — use frozenset:
a = frozenset({1, 2, 3})
b = frozenset({3, 4, 5})
print(a & b) # frozenset({3})
print(a | b) # frozenset({1, 2, 3, 4, 5})
# frozenset as a dict key
registry = {a: "group_a", b: "group_b"}
print(registry[frozenset({1, 2, 3})]) # group_a
# frozenset inside a set
container = {frozenset({1, 2}), frozenset({3, 4})}
print(container)
You will not use frozenset daily at the start. But you will see it in interviews and libraries often enough that recognising the name and purpose matters.
Collections Reference — Lists, Tuples, Sets Side by Side
| Need | Use |
|---|---|
| Ordered sequence you will edit in place | list |
| Fixed bundle of related values, hashable | tuple |
| Unique membership, fast lookup, overlap math | set |
| Unique immutable collection, usable as key | frozenset |
| Labeled fields with key access | dict (Day 7) |
One sentence to remember each: lists are editable sequences, tuples are fixed bundles, sets are unique bags, dicts are labeled drawers.
A Complete Working Program — Roster Overlap and Deduplication
This script shows how tuples and sets appear together in real code: tuples for stable records, sets for fast overlap analysis:
# Workshop registration system
# Each entry is a tuple: (student_id, name, email)
workshop_a = (
(101, "Ana Sharma", "ana@example.com"),
(102, "Ben Okafor", "ben@example.com"),
(103, "Cleo Park", "cleo@example.com"),
)
workshop_b = (
(103, "Cleo Park", "cleo@example.com"),
(104, "Dev Mehta", "dev@example.com"),
(105, "Eva Torres", "eva@example.com"),
)
# Extract IDs using set comprehensions
ids_a = {row[0] for row in workshop_a}
ids_b = {row[0] for row in workshop_b}
# Set algebra
both = ids_a & ids_b
only_a = ids_a - ids_b
only_b = ids_b - ids_a
all_ids = ids_a | ids_b
# Look up names for students in both workshops
all_students = {row[0]: row[1] for row in (*workshop_a, *workshop_b)}
print("=== Workshop Registration Report ===")
print(f"Workshop A enrolled: {len(ids_a)}")
print(f"Workshop B enrolled: {len(ids_b)}")
print(f"Total unique students: {len(all_ids)}")
print(f"\nIn both workshops: {[all_students[i] for i in sorted(both)]}")
print(f"Only workshop A: {[all_students[i] for i in sorted(only_a)]}")
print(f"Only workshop B: {[all_students[i] for i in sorted(only_b)]}")
Output:
=== Workshop Registration Report ===
Workshop A enrolled: 3
Workshop B enrolled: 3
Total unique students: 5
In both workshops: ['Cleo Park']
Only workshop A: ['Ana Sharma', 'Ben Okafor']
Only workshop B: ['Dev Mehta', 'Eva Torres']
Every concept from this chapter appears here — tuples for records, set comprehensions for extraction, set algebra for analysis, dict comprehension for lookup, unpacking in the final loop.
5 Mistakes Every Beginner Makes With Tuples and Sets
Mistake 1: Forgetting the comma in a one-element tuple
x = (10) # int — not a tuple
y = (10,) # tuple — comma makes it
print(type(x)) # <class 'int'>
print(type(y)) # <class 'tuple'>
Mistake 2: Using {} for an empty set
empty = {} # dict — wrong
empty = set() # set — correct
print(type({})) # <class 'dict'>
Mistake 3: Trying to index a set
s = {10, 20, 30}
# print(s[0]) # TypeError: 'set' object is not subscriptable
Sets have no order and no index. If you need positional access, convert to a sorted list first:
print(sorted(s)[0]) # 10
Mistake 4: Capturing append()-style return values from in-place methods
s = {1, 2, 3}
result = s.add(4)
print(result) # None — add() returns None, modifies in place
print(s) # {1, 2, 3, 4}
add(), remove(), discard(), update(), and clear() all return None. They modify in place.
Mistake 5: Putting unhashable items in a set
# bad = {[1, 2], [3, 4]} # TypeError: unhashable type: 'list'
good = {(1, 2), (3, 4)} # correct — tuples are hashable
print(good)
Practice: Tuples and Sets Exercises
Exercise 1: Tuple basics
Create a tuple of five programming languages. Print the first, last, and a slice of the middle three. Try to modify one item and observe the error.
Exercise 2: Unpacking
Given stats = (1250, 87, 0.348) representing (at-bats, hits, batting-average), unpack into three named variables and print a formatted summary line.
Exercise 3: Return multiple values
Write a function analyse(numbers) that takes a list and returns a tuple containing the minimum, maximum, sum, and average. Unpack the result when you call it.
Exercise 4: Deduplication
Given emails = ["user@a.com", "admin@b.com", "user@a.com", "support@c.com", "admin@b.com"], create a set of unique emails, print how many unique addresses exist, and check whether "ceo@d.com" is in the list.
Exercise 5: Set operations
Given team1 = {"maya", "li", "omar", "priya"} and team2 = {"li", "ava", "omar", "chen"}:
- Print who is on both teams
- Print who is only on team1
- Print the full combined roster sorted alphabetically
- Check whether team1 and team2 are disjoint
Exercise 6: Hashable composite keys
Build a dictionary mapping (latitude, longitude) tuples to place names for three locations. Look up one coordinate and print the place name.
→ See all Python practice exercises with solutions
For all topics, see Python exercises.
What Comes Next — Day 7: Dictionaries
Tuples and sets complete the story of collections without labels. Dictionaries add the missing piece: every value gets a key you choose. That structure drives JSON, API responses, configuration objects, and most Python programs beyond beginner scripts.
In Day 7 you will learn how to create dictionaries, access and update values, iterate over keys and values together, handle missing keys safely, and nest dictionaries for structured data.
→ Continue to Day 7: Dictionaries
Chapter navigation
- Previous: Day 5: Lists and List Methods
- Next: Day 7: Dictionaries
Frequently asked questions: Tuples and sets
What is the difference between a list and a tuple in Python?
Both are ordered sequences, but lists are mutable — you can append, pop, and assign to indexes — while tuples are immutable — once created, their length and elements cannot be changed in place. Tuples are hashable when all items are hashable, which means they can be used as dictionary keys and set members. Lists cannot. Use tuples for fixed records where immutability communicates intent, and lists for collections you need to modify.
Why does (1) not create a tuple in Python?
Parentheses serve two roles in Python: grouping expressions and delimiting tuple literals. Because (1) looks like a grouped integer expression, Python parses it as the integer 1. A one-element tuple requires a trailing comma — (1,) — to signal that a tuple is intended rather than a grouped value.
What is tuple unpacking in Python?
Tuple unpacking assigns multiple names from a sequence in one line: a, b, c = (1, 2, 3). The right-hand side can be any iterable of the matching length. Extended unpacking uses * to capture variable numbers of items into a list: first, *rest = values. This pattern appears constantly — in loops, function returns, and variable swapping.
What is a set in Python and when should I use one?
A set is an unordered collection of unique hashable items. Use a set when you need fast membership testing, deduplication, or set algebra (union, intersection, difference). Sets are mutable — you can add and remove items — but they have no index-based access because order is not preserved.
What is the difference between remove() and discard() for Python sets?
remove(x) deletes x and raises a KeyError if x is not present. discard(x) deletes x if it exists and does nothing silently if it is absent. Use discard() when absence is expected and normal. Use remove() when the item must exist and its absence indicates a bug that should surface immediately.
How do union, intersection, and difference work for Python sets?
Union (|) produces all elements from either set. Intersection (&) produces only elements present in both. Difference (-) produces elements in the first set but not the second. Symmetric difference (^) produces elements in either set but not both. These match standard mathematical set definitions and are highly optimised in CPython for large collections.
Why is {} an empty dict and not an empty set in Python?
Curly brace dict syntax existed before Python added set literals. Changing {} to mean empty set would break all existing code using {} for empty dicts. For backward compatibility, {} remains an empty dict. Create an empty set with set() — no arguments, no braces.
Can a Python tuple contain a list?
Yes, syntactically. But a tuple containing a list is not hashable — you cannot use it as a dictionary key or put it in a set. Hashability requires all items to be hashable, and lists are mutable and therefore not hashable. If you need a hashable composite structure, use a tuple of numbers, strings, or other tuples only.
What is a frozenset in Python?
A frozenset is an immutable version of a set. Like a regular set it contains unique hashable items and supports set algebra operations — but you cannot add or remove items after creation. Because it is immutable, a frozenset is itself hashable and can be used as a dictionary key or stored inside another set.
Related Page
Day 5: Lists and List Methods
Learn about day 5: lists and list methods
Learn MoreDay 7: Dictionaries
Continue with day 7: dictionaries
Learn MorePython exercises hub
165+ programs with solutions
Learn MorePython quiz
Multiple-choice checks with explanations
Learn MoreBasic Python programs
Includes swap with tuple unpacking
Learn More