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.
1# Curly brace literal2primes = {2, 3, 5, 7, 11}34# set() constructor: converts any iterable5from_list = set([1, 2, 2, 3, 3, 3]) # {1, 2, 3}6from_str = set("hello") # {'h', 'e', 'l', 'o'}: duplicates removed7from_range = set(range(5)) # {0, 1, 2, 3, 4}89# Empty set: must use set(), not {}10empty = set() # {} would create an empty dict11print(type(empty)) # <class 'set'>1213print(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.
1votes = ["yes", "no", "yes", "yes", "maybe", "no"]23unique_responses = set(votes)4print(unique_responses) # {'yes', 'no', 'maybe'}5print(len(unique_responses)) # 367# 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.
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.
1s = {1, 2, 3}23# add(): adds one element, no-op if already present4s.add(4)5s.add(2) # duplicate, silently ignored6print(s) # {1, 2, 3, 4}78# remove(): raises KeyError if missing9s.remove(4)10print(s) # {1, 2, 3}1112# discard(): same as remove, but never raises13s.discard(99) # no error even though 99 is not in s14print(s) # {1, 2, 3}1516# pop(): removes and returns an arbitrary element17item = s.pop()18print(item) # 1 (or any element, order is undefined)1920# clear(): empties the set21s.clear()22print(s) # set()
In-place operations
1permissions = {"read", "write"}23# |= union update: add all items from another set4permissions |= {"delete", "admin"}5print(permissions) # {'read', 'write', 'delete', 'admin'}67# -= difference update: remove items found in another set8permissions -= {"admin"}9print(permissions) # {'read', 'write', 'delete'}1011# &= intersection update: keep only shared items12permissions &= {"read", "write", "execute"}13print(permissions) # {'read', 'write'}1415# 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.
1A = {1, 2, 3, 4, 5}2B = {4, 5, 6, 7, 8}34# Union: all items from either set5print(A | B) # {1, 2, 3, 4, 5, 6, 7, 8}6print(A.union(B)) # same78# Intersection: items in both9print(A & B) # {4, 5}10print(A.intersection(B)) # same1112# Difference: in A but not B13print(A - B) # {1, 2, 3}14print(A.difference(B)) # same1516# Symmetric difference: in exactly one set17print(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.
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.
1banned = {"spam", "abuse", "malware"}23# O(1): same speed for 3 items or 3 million4print("spam" in banned) # True5print("python" in banned) # False6print("malware" not in banned) # False78# Lists use O(n) linear scan, slower for large collections9banned_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.
1import time23items = list(range(1_000_000))4items_set = set(items)5target = 999_99967# List lookup: scans up to 1M elements8start = time.perf_counter()9found = target in items10list_time = time.perf_counter() - start1112# Set lookup: O(1) hash check13start = time.perf_counter()14found = target in items_set15set_time = time.perf_counter() - start1617print(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.
1required = {"read", "write"}2user_perms = {"read", "write", "delete"}34# Is required a subset of user_perms?5print(required <= user_perms) # True (required is a subset of user_perms)6print(required.issubset(user_perms)) # True78# Is user_perms a superset of required?9print(user_perms >= required) # True10print(user_perms.issuperset(required)) # True1112# Disjoint: no elements in common13admin = {"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.
1def has_duplicates(items) -> bool:2 return len(items) != len(set(items))34print(has_duplicates([1, 2, 3, 4])) # False5print(has_duplicates([1, 2, 2, 3])) # True6print(has_duplicates("abcde")) # False7print(has_duplicates("hello")) # True
Finding common and unique elements
1users_a = {"alice", "bob", "carol", "dan"}2users_b = {"carol", "dan", "eve", "frank"}34# Who is in both groups?5both = users_a & users_b6print("both:", both) # {'carol', 'dan'}78# Who is only in group A?9a_only = users_a - users_b10print("A only:", a_only) # {'alice', 'bob'}1112# Who appears in either group, just once?13exclusive = users_a ^ users_b14print("exclusive:", exclusive) # {'alice', 'bob', 'eve', 'frank'}1516# Everyone combined17everyone = users_a | users_b18print("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.
1# frozenset is immutable and hashable2fs = frozenset([1, 2, 3])3print(fs) # frozenset({1, 2, 3})45# Can be used as a dict key6pair_scores = {7 frozenset({"Alice", "Bob"}): 10,8 frozenset({"Alice", "Carol"}): 7,9}10key = frozenset({"Bob", "Alice"}) # order does not matter11print(pair_scores[key]) # 101213# Can be placed inside a set14seen_pairs = set()15seen_pairs.add(frozenset({"Alice", "Bob"}))16print(frozenset({"Bob", "Alice"}) in seen_pairs) # True
Set comprehensions
1words = ["hello", "world", "hello", "python", "world"]23# Unique lengths4lengths = {len(w) for w in words}5print(lengths) # {5, 6}67# Unique first letters8initials = {w[0].upper() for w in words}9print(initials) # {'H', 'W', 'P'}1011# Filter and deduplicate in one step12long_words = {w for w in words if len(w) > 4}13print(long_words) # {'hello', 'world', 'python'}