Python Sets: The Complete Guide with All Methods
Master Python sets — creation, uniqueness, set algebra, and every built-in method (union, intersection, difference, symmetric_difference, and more) with runnable examples and the mutating-vs-returning distinction.
Python’s set is an unordered collection of unique, hashable elements. It’s the tool you reach for whenever you need fast membership tests, need to strip duplicates from a sequence, or want to perform mathematical set operations like union and intersection directly in code. This guide covers everything from how sets are created to a full method-by-method reference with runnable examples.
Set Fundamentals
Uniqueness and hashability
A set can never contain duplicate elements — adding a value that’s already present is a silent no-op. Every element of a set must also be hashable, which means immutable types like int, str, float, and tuple (of hashable items) work fine, but mutable types like list, dict, and other set objects cannot be stored inside a set.
1
2
3
4
s = {1, 2, 2, 3}
print(s) # {1, 2, 3}
# s.add([1, 2]) # TypeError: unhashable type: 'list'
Creating sets
There are three common ways to build a set:
1
2
3
4
5
literal = {1, 2, 3}
from_constructor = set([1, 2, 2, 3]) # {1, 2, 3}
from_comprehension = {x * x for x in range(5)} # {0, 1, 4, 9, 16}
empty = set() # NOT {} — that creates an empty dict
frozenset — the immutable sibling
frozenset behaves like set but cannot be mutated after creation, which makes it hashable and usable as a dictionary key or as an element inside another set.
1
2
fs = frozenset([1, 2, 3])
d = {fs: "immutable key works"}
Membership and iteration
Membership tests on a set run in average O(1) time, versus O(n) for a list, which is the main reason to reach for a set when checking “is this value present” repeatedly.
1
2
3
4
5
colors = {"red", "green", "blue"}
print("red" in colors) # True
for c in colors: # order is not guaranteed
print(c)
Sets are unordered
Because sets are hash-based, they don’t preserve insertion order and don’t support indexing (colors[0] raises TypeError). If you need order, use a list or, since Python 3.7, rely on dict (which preserves insertion order) instead.
Set Methods
Below is every core set method, grouped with its signature, a short runnable example, and — critically — whether it returns a new set or mutates the set in place.
set.add()
Adds a single element to the set. Mutates in place; returns None.
1
2
3
s = {1, 2}
s.add(3)
print(s) # {1, 2, 3}
set.clear()
Removes all elements from the set. Mutates in place; returns None.
1
2
3
s = {1, 2, 3}
s.clear()
print(s) # set()
set.copy()
Returns a shallow copy of the set — a genuinely new object, so mutating the copy does not affect the original.
1
2
3
4
s = {1, 2, 3}
c = s.copy()
c.add(4)
print(s, c) # {1, 2, 3} {1, 2, 3, 4}
set.difference()
set.difference(*others) — returns a new set of elements in set that are not in any of others. Equivalent to -.
1
2
3
4
a = {1, 2, 3}
b = {2, 3, 4}
print(a.difference(b)) # {1}
print(a - b) # {1}
set.difference_update()
Same computation as difference(), but mutates set in place instead of returning a new one.
1
2
3
4
a = {1, 2, 3}
b = {2, 3, 4}
a.difference_update(b)
print(a) # {1}
set.discard()
Removes an element if present; unlike remove(), does not raise if the element is missing. Mutates in place; returns None.
1
2
3
4
s = {1, 2, 3}
s.discard(5) # no error
s.discard(2)
print(s) # {1, 3}
set.intersection()
set.intersection(*others) — returns a new set of elements common to set and all others. Equivalent to &.
1
2
3
4
a = {1, 2, 3}
b = {2, 3, 4}
print(a.intersection(b)) # {2, 3}
print(a & b) # {2, 3}
set.intersection_update()
Same computation as intersection(), but mutates set in place.
1
2
3
4
a = {1, 2, 3}
b = {2, 3, 4}
a.intersection_update(b)
print(a) # {2, 3}
set.isdisjoint()
Returns True if set and other share no elements. Read-only — never mutates.
1
2
3
a = {1, 2}
b = {3, 4}
print(a.isdisjoint(b)) # True
set.issubset()
Returns True if every element of set is also in other. Equivalent to <=. Read-only.
1
2
3
4
a = {1, 2}
b = {1, 2, 3}
print(a.issubset(b)) # True
print(a <= b) # True
set.issuperset()
Returns True if set contains every element of other. Equivalent to >=. Read-only.
1
2
3
4
a = {1, 2, 3}
b = {1, 2}
print(a.issuperset(b)) # True
print(a >= b) # True
set.pop()
Removes and returns an arbitrary element. Mutates in place; raises KeyError on an empty set.
1
2
3
s = {1, 2, 3}
x = s.pop()
print(x, s) # e.g. 1 {2, 3} — the popped value isn't guaranteed
set.remove()
Removes a specific element. Mutates in place; raises KeyError if the element is absent (use discard() if you want a silent no-op).
1
2
3
s = {1, 2, 3}
s.remove(2)
print(s) # {1, 3}
set.symmetric_difference()
Returns a new set of elements in exactly one of the two sets (present in either, not both). Equivalent to ^.
1
2
3
4
a = {1, 2, 3}
b = {2, 3, 4}
print(a.symmetric_difference(b)) # {1, 4}
print(a ^ b) # {1, 4}
set.symmetric_difference_update()
Same computation as symmetric_difference(), but mutates set in place.
1
2
3
4
a = {1, 2, 3}
b = {2, 3, 4}
a.symmetric_difference_update(b)
print(a) # {1, 4}
set.union()
set.union(*others) — returns a new set containing every element from set and all others. Equivalent to |.
1
2
3
4
a = {1, 2}
b = {2, 3}
print(a.union(b)) # {1, 2, 3}
print(a | b) # {1, 2, 3}
set.update()
Same computation as union(), but mutates set in place, adding elements from the other iterables.
1
2
3
4
a = {1, 2}
b = {2, 3}
a.update(b)
print(a) # {1, 2, 3}
Method vs. Operator Cheat Sheet
| Method | Operator equivalent | Returns new or mutates |
|---|---|---|
union() | \| | Returns new |
update() | \|= | Mutates |
intersection() | & | Returns new |
intersection_update() | &= | Mutates |
difference() | - | Returns new |
difference_update() | -= | Mutates |
symmetric_difference() | ^ | Returns new |
symmetric_difference_update() | ^= | Mutates |
issubset() | <= | Returns bool |
issuperset() | >= | Returns bool |
add() | — | Mutates |
remove() | — | Mutates (raises if missing) |
discard() | — | Mutates (silent if missing) |
pop() | — | Mutates, returns removed item |
clear() | — | Mutates |
copy() | — | Returns new |
isdisjoint() | — | Returns bool |
The pattern to remember: the operator forms (|, &, -, ^) and their plain method names always return a new set, while the _update (or =-suffixed operator) forms mutate the original in place — mirroring how + vs += behaves for lists.
Common Use Cases
Deduplicating a list. The fastest way to remove duplicates while discarding order is list(set(my_list)). If you need to preserve order, use dict.fromkeys(my_list) instead, since sets don’t guarantee ordering.
1
2
raw = [3, 1, 2, 3, 1, 4]
unique = list(set(raw)) # order not guaranteed
Fast membership checks. Converting a large list to a set before running many in checks turns an O(n) lookup into an O(1) one, which matters a lot in loops.
1
2
3
4
allowed = set(load_allowed_ids()) # once
for record in records:
if record.id in allowed: # O(1) each time
process(record)
Finding overlaps and differences between datasets. Set algebra is the natural fit whenever you’re comparing two collections — for example, which tags appear in both of two articles, or which users unsubscribed since last month.
1
2
3
4
tags_a = {"python", "ai", "tutorial"}
tags_b = {"python", "ml"}
shared = tags_a & tags_b # {'python'}
only_in_a = tags_a - tags_b # {'ai', 'tutorial'}
Performance Notes
Sets in CPython are implemented as hash tables, the same underlying structure as dictionaries (minus the values). That gives add, remove, discard, and in average O(1) time complexity, compared to O(n) for the equivalent list operations. The tradeoff is memory overhead — a set generally uses more memory per element than a list holding the same items — and the loss of ordering and indexing. For small collections (a handful of items) the difference is negligible; for anything processed in a hot loop or checked against repeatedly, sets are almost always the better choice over lists.
Common Pitfalls
- Empty set literal.
{}creates adict, not aset. Always useset()for an empty set. - Unhashable elements. Trying to put a
listordictinside a set raisesTypeError: unhashable type. Convert nested lists to tuples first if you need to store them. - Assuming order. Because sets are unordered, don’t rely on iteration order or the value returned by
pop()being predictable — if you need determinism, sort the set or use a list. - Confusing
remove()anddiscard(). Usediscard()when the element might not be present and you don’t want an exception; useremove()when its absence indicates a bug you want surfaced.
Where sets fit with the rest of Python
If you’re comparing sets against Python’s other core containers, the Python Lists: The Complete Guide covers ordered, index-based collections, and many set operations (like sorted(some_set)) lean on the Python built-in functions reference for things like len(), sorted(), and map(). Sets are also the natural structure for deduplicating tokens or vocabulary when building retrieval pipelines — see the Beginner’s Guide to LangChain in Python for a practical case where fast membership checks matter.
