🌱 Beginner

Tuples & Sets

📖 Lesson 07 ⏱ 25 min 🧪 5 questions 💻 3 exercises

🎯 Learning Objectives

  • Create tuples and understand their immutability
  • Use tuple packing, unpacking, and named tuples
  • Know when to choose a tuple over a list
  • Create sets and understand uniqueness & unordered nature
  • Perform set operations: union, intersection, difference, symmetric difference
  • Use frozenset for immutable sets

Tuples

A tuple is an ordered, immutable sequence. Once created, you cannot add, remove, or change its elements.

# Creating tuples
point = (3, 4)
rgb = (255, 128, 0)
single = (42,)          # trailing comma required for single-element tuple!
empty = ()
mixed = ("hello", 3.14, True)

# Parentheses are optional (it's the commas that make a tuple)
coords = 10, 20, 30
print(type(coords))  # <class 'tuple'>

# From other iterables
t = tuple([1, 2, 3])     # from list
t2 = tuple("hello")      # ('h', 'e', 'l', 'l', 'o')
create_tuples.py
A single value in parentheses is not a tuple: (42) is just the integer 42. You need a trailing comma: (42,).

Indexing & Slicing

Same rules as lists — zero-based, negative indices, slicing returns a new tuple:

colors = ("red", "green", "blue", "yellow")

print(colors[0])      # "red"
print(colors[-1])     # "yellow"
print(colors[1:3])    # ("green", "blue")
print(len(colors))    # 4
print("red" in colors)  # True
tuple_access.py

Immutability

t = (1, 2, 3)

# These all raise TypeError:
# t[0] = 99
# t.append(4)
# del t[1]

# But! If a tuple CONTAINS a mutable object, that object can change:
tricky = ([1, 2], [3, 4])
tricky[0].append(99)
print(tricky)  # ([1, 2, 99], [3, 4]) — the list inside changed!
immutability.py
Tuple immutability means you can't reassign its elements (the references). But if an element is itself mutable (like a list), the object it points to can still change.

Tuple Packing & Unpacking

# Packing — assigning multiple values creates a tuple
coordinates = 4, 5, 6

# Unpacking — assign tuple elements to separate variables
x, y, z = coordinates
print(x, y, z)  # 4 5 6

# Swap values (uses tuple packing/unpacking under the hood)
a, b = 1, 2
a, b = b, a
print(a, b)  # 2 1

# Star unpacking
first, *rest = (1, 2, 3, 4, 5)
print(first)  # 1
print(rest)   # [2, 3, 4, 5]  ← note: rest is a list

# Ignore values with _
_, y, _ = (10, 20, 30)
print(y)  # 20

# Functions can return multiple values as a tuple
def min_max(numbers):
    return min(numbers), max(numbers)

lo, hi = min_max([4, 8, 1, 9])
print(lo, hi)  # 1 9
unpacking.py
Returning multiple values from a function using tuples is a very common Python pattern. The caller can unpack them cleanly: x, y = get_coords().

Named Tuples

When tuple positions have specific meanings, use namedtuple for self-documenting code:

from collections import namedtuple

# Define a named tuple type
Point = namedtuple("Point", ["x", "y"])
Color = namedtuple("Color", "r g b")

# Create instances
p = Point(3, 4)
c = Color(255, 128, 0)

# Access by name (much clearer than index)
print(p.x, p.y)      # 3 4
print(c.r, c.g, c.b) # 255 128 0

# Still works like a regular tuple
print(p[0])       # 3
x, y = p          # unpacking works
print(len(c))     # 3
named_tuples.py

When to Use a Tuple vs a List

Use a Tuple when…Use a List when…
Data shouldn't change (coordinates, RGB, config)Data will grow/shrink (shopping cart, logs)
You need a hashable type (dict keys, set elements)Order matters and you need mutation
Returning multiple values from a functionCollecting items in a loop
You want to signal "this is fixed"You want to signal "this will change"

Sets

A set is an unordered collection of unique elements. Duplicates are automatically removed.

# Creating sets
fruits = {"apple", "banana", "cherry"}
numbers = {1, 2, 3, 2, 1}    # duplicates removed
print(numbers)  # {1, 2, 3}

# Empty set — must use set(), NOT {} (that's an empty dict!)
empty = set()

# From other iterables
from_list = set([1, 2, 2, 3, 3, 3])   # {1, 2, 3}
from_string = set("hello")             # {'h', 'e', 'l', 'o'}

# Sets can only contain HASHABLE (immutable) items
valid = {1, "hi", (1, 2), True}
# invalid = {[1, 2]}  ← TypeError: unhashable type: 'list'
create_sets.py
Sets are unordered — they have no index and no guaranteed order when printed or iterated. Don't rely on element order in a set.

Modifying Sets

s = {1, 2, 3}

# Add a single element
s.add(4)           # {1, 2, 3, 4}
s.add(2)           # no effect — already present

# Remove elements
s.remove(3)        # {1, 2, 4} — raises KeyError if missing
s.discard(99)      # no error if missing
popped = s.pop()   # removes & returns an arbitrary element
s.clear()          # empty set

# Add multiple elements
s = {1, 2}
s.update([3, 4, 5])     # {1, 2, 3, 4, 5}
s.update("abc")          # adds 'a', 'b', 'c'
set_methods.py
Use .discard() over .remove() when you're not sure the element exists — it silently does nothing instead of raising an error.

Set Operations

Sets support powerful mathematical operations:

OperationOperatorMethodResult
Uniona | ba.union(b)All elements from both
Intersectiona & ba.intersection(b)Elements in both
Differencea - ba.difference(b)In a but not in b
Symmetric diffa ^ ba.symmetric_difference(b)In one but not both
a = {1, 2, 3, 4, 5}
b = {4, 5, 6, 7, 8}

print(a | b)   # {1, 2, 3, 4, 5, 6, 7, 8}  — union
print(a & b)   # {4, 5}                      — intersection
print(a - b)   # {1, 2, 3}                   — difference
print(b - a)   # {6, 7, 8}                   — difference (other way)
print(a ^ b)   # {1, 2, 3, 6, 7, 8}         — symmetric difference

# Subset / superset checks
small = {1, 2}
big = {1, 2, 3, 4, 5}
print(small <= big)    # True  (small is a subset of big)
print(big >= small)    # True  (big is a superset of small)
print(small < big)     # True  (proper subset — not equal)

# Disjoint check — no common elements
print({1, 2}.isdisjoint({3, 4}))  # True
set_operations.py

Practical Set Patterns

# Remove duplicates from a list (fast but loses order)
items = [1, 3, 2, 3, 1, 4, 2]
unique = list(set(items))
print(unique)  # order not guaranteed

# Preserve order while removing duplicates (Python 3.7+)
unique_ordered = list(dict.fromkeys(items))
print(unique_ordered)  # [1, 3, 2, 4]

# Fast membership testing (O(1) vs O(n) for lists)
valid_codes = {"US", "UK", "CA", "AU", "DE", "FR"}
user_code = "CA"
if user_code in valid_codes:
    print("Valid country")

# Find common friends
alice_friends = {"Bob", "Charlie", "Dave", "Eve"}
bob_friends = {"Alice", "Charlie", "Eve", "Frank"}
mutual = alice_friends & bob_friends
print(mutual)  # {'Charlie', 'Eve'}

# Find skills a candidate is missing
required = {"python", "sql", "git", "docker"}
candidate = {"python", "git", "javascript"}
missing = required - candidate
print(f"Missing: {missing}")  # {'sql', 'docker'}
set_patterns.py
Checking x in set is O(1) on average — constant time regardless of set size. For lists it's O(n). If you're doing many membership checks, convert to a set first.

Frozenset

A frozenset is an immutable set. It supports all read operations and set math, but cannot be modified. Because it's immutable, it can be used as a dictionary key or an element of another set.

fs = frozenset([1, 2, 3, 4])

# All read operations work
print(3 in fs)      # True
print(fs | {5, 6}) # frozenset({1, 2, 3, 4, 5, 6})

# Mutation is not allowed
# fs.add(5)  ← AttributeError

# Can be used as a dict key or set element
cache = {frozenset({1, 2}): "result_a"}
nested_sets = {frozenset({1, 2}), frozenset({3, 4})}
frozenset.py
🤖

Ask your AI tutor! Not sure when to use a tuple vs a list vs a set? Confused about why {} is a dict and not an empty set? These are common points of confusion — ask and get clarity.

💻 Exercises

01 Tuple Unpacking

Write a function stats(numbers) that takes a list of numbers and returns a tuple of (minimum, maximum, average). Call it and unpack the result into three variables.

Show solution
def stats(numbers):
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

data = [23, 45, 12, 67, 34, 89, 2]
lo, hi, avg = stats(data)
print(f"Min: {lo}, Max: {hi}, Avg: {avg:.2f}")
# Min: 2, Max: 89, Avg: 38.86
02 Common Elements

Given two lists: [1, 2, 3, 4, 5, 6] and [4, 5, 6, 7, 8, 9], use set operations to find: elements in both, elements only in the first, and all unique elements combined. Print each result.

Show solution
list_a = [1, 2, 3, 4, 5, 6]
list_b = [4, 5, 6, 7, 8, 9]

set_a = set(list_a)
set_b = set(list_b)

common = set_a & set_b
only_a = set_a - set_b
all_unique = set_a | set_b

print(f"In both:      {sorted(common)}")     # [4, 5, 6]
print(f"Only in A:    {sorted(only_a)}")     # [1, 2, 3]
print(f"All unique:   {sorted(all_unique)}") # [1, 2, 3, 4, 5, 6, 7, 8, 9]
03 Word Frequency Unique

Given the string "the cat sat on the mat the cat", find: how many total words there are, how many unique words, and list the unique words sorted alphabetically.

Show solution
text = "the cat sat on the mat the cat"
words = text.split()
unique_words = set(words)

print(f"Total words:  {len(words)}")          # 8
print(f"Unique words: {len(unique_words)}")   # 5
print(f"Sorted:       {sorted(unique_words)}")
# ['cat', 'mat', 'on', 'sat', 'the']