C.W.K.
Stream
Lesson 05 of 07 · published

Sets and Frozenset — Math, Membership, Dedup

~18 min · set, frozenset, membership, set-math

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

The collection you'll use less often, but at exactly the right moments

Sets are unordered collections of unique, hashable elements. They're not as common in everyday code as list or dict, but when you need them, no other collection comes close: dedup, fast membership checks, and the four set operations (union, intersection, difference, symmetric difference). The literal syntax is {1, 2, 3}; the empty set is set() (because {} is an empty dict).

Why O(1) membership matters

Checking x in my_list is O(n) — Python walks the list. Checking x in my_set is O(1) — a hash lookup. If you're going to test "is this thing in this collection" thousands of times, switching the collection from a list to a set is the single biggest performance win you can do without changing your algorithm.

Set math — unions, intersections, differences

Sets support actual mathematical set operations, both with operators and with method names. a | b (or a.union(b)) returns elements in either. a & b (intersection) returns elements in both. a - b (difference) returns elements in a but not b. a ^ b (symmetric difference) returns elements in exactly one. The methods accept any iterable; the operators only accept other sets.

Principle: If you're tempted to write a nested for-loop to find "items in A but not in B," stop and use set(A) - set(B). This pattern recognition takes a few months but it's transformative.

Sets are unordered — and that's not a bug

Sets do not preserve insertion order. Iterating a set gives you elements in some order — often (but not always) hash order. If order matters, use a list. If you want unique and ordered, the modern idiom is list(dict.fromkeys(items)), since dicts are insertion-ordered.

Frozenset — when you need a set as a hashable

A regular set is mutable, so it can't be a dict key or live inside another set. frozenset is the immutable version. Same operations, no .add()/.remove(), fully hashable. The use case: you want a key that represents an unordered collection (like a multi-tag combination, or a graph edge as frozenset({u, v})).

Code

Creating sets — and the empty-set trap·python
# Literal
s = {1, 2, 3}

# From an iterable — also dedups
s = set([1, 2, 2, 3, 3, 3])
print(s)                  # {1, 2, 3}

# Empty set — must use set(), not {}
empty = set()             # this is an empty SET
empty_dict = {}           # this is an empty DICT

print(type(empty))        # <class 'set'>
print(type(empty_dict))   # <class 'dict'>
Membership: O(1) vs O(n)·python
import time

big_list = list(range(1_000_000))
big_set = set(big_list)

start = time.perf_counter()
result = 999_999 in big_list      # O(n) — walks the list
print("list:", time.perf_counter() - start)

start = time.perf_counter()
result = 999_999 in big_set       # O(1) — hash lookup
print("set:", time.perf_counter() - start)
# list is typically thousands of times slower
Set math — operators and methods·python
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

# Union
print(a | b)              # {1, 2, 3, 4, 5, 6}
print(a.union(b))         # same

# Intersection
print(a & b)              # {3, 4}
print(a.intersection(b))  # same

# Difference
print(a - b)              # {1, 2}
print(a.difference(b))    # same

# Symmetric difference — in either, but not both
print(a ^ b)              # {1, 2, 5, 6}

# Subset / superset
print({1, 2} <= a)        # True   — subset
print(a >= {1, 2})        # True   — superset
Dedup while preserving order — dict trick·python
items = ["apple", "banana", "apple", "cherry", "banana"]

# set() dedups but loses order
print(set(items))                 # order undefined

# Want unique AND ordered? Use dict.fromkeys() — dicts ARE ordered
print(list(dict.fromkeys(items))) # ['apple', 'banana', 'cherry']  — first-seen order
Frozenset — hashable so it can be a key·python
# Regular set is unhashable
edge_lookup = {}
try:
    edge_lookup[{1, 2}] = "edge"
except TypeError as e:
    print(e)              # unhashable type: 'set'

# frozenset is hashable
edge_lookup[frozenset({1, 2})] = "edge A"
edge_lookup[frozenset({2, 3})] = "edge B"

# Order doesn't matter — same edge
print(edge_lookup[frozenset({2, 1})])    # 'edge A'

External links

Exercise

You have two lists: users_2024 = ['alice', 'bob', 'charlie', 'dave'] and users_2025 = ['bob', 'charlie', 'eve', 'frank']. Without using any explicit loops, compute and print: (a) users in both years, (b) users only in 2024, (c) users only in 2025, (d) users active in exactly one year. Convert the result to a sorted list before printing each one.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.