Codetail

Article 8 of 15

Sets

Uniqueness, membership, set math.

14 min read

Creating sets

A set is an unordered collection of unique elements. Each value appears at most once. Adding a duplicate does nothing. Sets are backed by a hash table, membership tests are O(1) regardless of size.

Python
1# Curly brace literal
2primes = {2, 3, 5, 7, 11}
3
4# set() constructor: converts any iterable
5from_list = set([1, 2, 2, 3, 3, 3]) # {1, 2, 3}
6from_str = set("hello") # {'h', 'e', 'l', 'o'}: duplicates removed
7from_range = set(range(5)) # {0, 1, 2, 3, 4}
8
9# Empty set: must use set(), not {}
10empty = set() # {} would create an empty dict
11print(type(empty)) # <class 'set'>
12
13print(from_list) # {1, 2, 3}
14print(from_str) # {'h', 'e', 'l', 'o'}

Sets are unordered. Python does not guarantee any particular iteration order. Never write code that depends on the order items come out of a set.

Uniqueness is enforced automatically

The most common use of set() is deduplication: convert a list to a set and every duplicate disappears. The order of remaining items is not preserved.

Python
1votes = ["yes", "no", "yes", "yes", "maybe", "no"]
2
3unique_responses = set(votes)
4print(unique_responses) # {'yes', 'no', 'maybe'}
5print(len(unique_responses)) # 3
6
7# Deduplicate while preserving order (Python 3.7+)
8seen = set()
9dedup = [x for x in votes if not (x in seen or seen.add(x))]
10print(dedup) # ['yes', 'no', 'maybe']

Watch how set() collapses duplicates. Strikethrough items in the list are the ones that get dropped.

Deduplication Explorerset(items) removes duplicates
Pick a list
list, 9 items6 duplicates
yes
no
yes
yes
maybe
no
yes
maybe
no
set(items)
set, 3 unique items
yes
no
maybe

9 items in, 3 items out, 6 removed

Modifying sets

Sets are mutable. You can add and remove elements after creation. The in-place operators (|=, &=, -=, ^=) update a set in place.

Python
1s = {1, 2, 3}
2
3# add(): adds one element, no-op if already present
4s.add(4)
5s.add(2) # duplicate, silently ignored
6print(s) # {1, 2, 3, 4}
7
8# remove(): raises KeyError if missing
9s.remove(4)
10print(s) # {1, 2, 3}
11
12# discard(): same as remove, but never raises
13s.discard(99) # no error even though 99 is not in s
14print(s) # {1, 2, 3}
15
16# pop(): removes and returns an arbitrary element
17item = s.pop()
18print(item) # 1 (or any element, order is undefined)
19
20# clear(): empties the set
21s.clear()
22print(s) # set()

In-place operations

Python
1permissions = {"read", "write"}
2
3# |= union update: add all items from another set
4permissions |= {"delete", "admin"}
5print(permissions) # {'read', 'write', 'delete', 'admin'}
6
7# -= difference update: remove items found in another set
8permissions -= {"admin"}
9print(permissions) # {'read', 'write', 'delete'}
10
11# &= intersection update: keep only shared items
12permissions &= {"read", "write", "execute"}
13print(permissions) # {'read', 'write'}
14
15# update() accepts any iterable (like |= but more flexible)
16permissions.update(["append", "read"])
17print(permissions) # {'read', 'write', 'append'}

Set operations

Sets support the four classic math operations: union, intersection, difference, and symmetric difference. Each produces a new set without modifying the originals. Python provides both operator syntax and method syntax, they are equivalent.

Python
1A = {1, 2, 3, 4, 5}
2B = {4, 5, 6, 7, 8}
3
4# Union: all items from either set
5print(A | B) # {1, 2, 3, 4, 5, 6, 7, 8}
6print(A.union(B)) # same
7
8# Intersection: items in both
9print(A & B) # {4, 5}
10print(A.intersection(B)) # same
11
12# Difference: in A but not B
13print(A - B) # {1, 2, 3}
14print(A.difference(B)) # same
15
16# Symmetric difference: in exactly one set
17print(A ^ B) # {1, 2, 3, 6, 7, 8}
18print(A.symmetric_difference(B)) # same

The method forms (.union(), .intersection(), etc.) accept any iterable, not just sets. The operators (|, &, etc.) require both sides to be sets.

Pick an operation below to see which region of the Venn diagram it selects.

Set Operations ExplorerA = {1, 2, 3, 4, 5} ยท B = {4, 5, 6, 7, 8}
AB1 23456 78
Try it, pick an operation

Membership testing

The in operator is the main reason to reach for a set. Checking whether an item exists in a list is O(n), Python scans every element until it finds a match or runs out of items. Checking membership in a set is O(1), the hash is computed once and Python jumps directly to the right slot.

Python
1banned = {"spam", "abuse", "malware"}
2
3# O(1): same speed for 3 items or 3 million
4print("spam" in banned) # True
5print("python" in banned) # False
6print("malware" not in banned) # False
7
8# Lists use O(n) linear scan, slower for large collections
9banned_list = ["spam", "abuse", "malware"]
10print("spam" in banned_list) # True: but scans from the start

When to use a set for lookups

If you have a fixed collection and need to test membership repeatedly, convert it to a set once. The upfront cost is paid back immediately on the first lookup.

Python
1import time
2
3items = list(range(1_000_000))
4items_set = set(items)
5target = 999_999
6
7# List lookup: scans up to 1M elements
8start = time.perf_counter()
9found = target in items
10list_time = time.perf_counter() - start
11
12# Set lookup: O(1) hash check
13start = time.perf_counter()
14found = target in items_set
15set_time = time.perf_counter() - start
16
17print(f"list: {list_time * 1000:.3f} ms")
18print(f"set: {set_time * 1000:.3f} ms")

Subset and superset tests

Sets support subset and superset comparisons with operators and methods. These are more expressive than manually checking every element.

Python
1required = {"read", "write"}
2user_perms = {"read", "write", "delete"}
3
4# Is required a subset of user_perms?
5print(required <= user_perms) # True (required is a subset of user_perms)
6print(required.issubset(user_perms)) # True
7
8# Is user_perms a superset of required?
9print(user_perms >= required) # True
10print(user_perms.issuperset(required)) # True
11
12# Disjoint: no elements in common
13admin = {"sudo", "root"}
14print(required.isdisjoint(admin)) # True

Real-world patterns

Fast duplicate detection

Comparing len(items) against len(set(items)) tells you instantly whether any duplicates exist, with no explicit loop.

Python
1def has_duplicates(items) -> bool:
2 return len(items) != len(set(items))
3
4print(has_duplicates([1, 2, 3, 4])) # False
5print(has_duplicates([1, 2, 2, 3])) # True
6print(has_duplicates("abcde")) # False
7print(has_duplicates("hello")) # True

Finding common and unique elements

Python
1users_a = {"alice", "bob", "carol", "dan"}
2users_b = {"carol", "dan", "eve", "frank"}
3
4# Who is in both groups?
5both = users_a & users_b
6print("both:", both) # {'carol', 'dan'}
7
8# Who is only in group A?
9a_only = users_a - users_b
10print("A only:", a_only) # {'alice', 'bob'}
11
12# Who appears in either group, just once?
13exclusive = users_a ^ users_b
14print("exclusive:", exclusive) # {'alice', 'bob', 'eve', 'frank'}
15
16# Everyone combined
17everyone = users_a | users_b
18print("total:", len(everyone)) # 6

frozenset as a dict key

Sets are mutable, so they cannot be used as dict keys or placed inside sets. frozenset is the immutable version, hashable, usable anywhere a set's structure is needed but mutability is not.

Python
1# frozenset is immutable and hashable
2fs = frozenset([1, 2, 3])
3print(fs) # frozenset({1, 2, 3})
4
5# Can be used as a dict key
6pair_scores = {
7 frozenset({"Alice", "Bob"}): 10,
8 frozenset({"Alice", "Carol"}): 7,
9}
10key = frozenset({"Bob", "Alice"}) # order does not matter
11print(pair_scores[key]) # 10
12
13# Can be placed inside a set
14seen_pairs = set()
15seen_pairs.add(frozenset({"Alice", "Bob"}))
16print(frozenset({"Bob", "Alice"}) in seen_pairs) # True

Set comprehensions

Python
1words = ["hello", "world", "hello", "python", "world"]
2
3# Unique lengths
4lengths = {len(w) for w in words}
5print(lengths) # {5, 6}
6
7# Unique first letters
8initials = {w[0].upper() for w in words}
9print(initials) # {'H', 'W', 'P'}
10
11# Filter and deduplicate in one step
12long_words = {w for w in words if len(w) > 4}
13print(long_words) # {'hello', 'world', 'python'}