Python: Zero to Hero
Home/Python Essentials
Share

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?

  1. A frozenset can be used as a dictionary key (a regular set cannot).
  2. A frozenset can 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(), and index().
  • Tuple unpacking assigns multiple variables at once: a, b = (1, 2).
  • Extended unpacking: first, *rest = tuple collects 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 {} or set().
  • Sets automatically remove duplicates.
  • add() adds one item. discard() removes without error. remove() raises KeyError.
  • Membership testing with in is much faster on sets than lists.
  • Set operations: | union, & intersection, - difference, ^ symmetric difference.
  • frozenset is 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.

© 2026 Abhilash Sahoo. Python: Zero to Hero.