Python · Intermediate
Sets in Python
In short: A set is an unordered collection of unique, hashable values. Use it to remove duplicates, to test membership quickly with the in operator, and to combine collections with union (|), intersection (&), difference (-) and symmetric difference (^). Sort a set before printing, because its order is not guaranteed.
What a set is and when it wins
A set holds each value at most once and does not remember the order in which values were added. Those two properties make it the right container whenever the question is "is this here?" or "which of these are also in those?" rather than "what is the third item?".
Membership is the big win. Testing x in some_list walks the list from the start and compares every element, so it slows down as the list grows. A set stores values by their hash, so x in some_set takes roughly the same time whether the set has ten members or ten million. Any time a loop checks whether something has already been seen, a set is the natural choice.
Because members are looked up by hash, they must be hashable: numbers, strings, booleans, tuples of hashable values and frozensets qualify; lists, dictionaries and other sets do not. That is why a set of coordinate pairs works with tuples but not with lists.
Sets support the algebra you may remember from school. a | b gives everything in either, a & b everything in both, a - b what is in a but not b, and a ^ b what is in exactly one of them. a <= b asks whether a is a subset of b, and a.isdisjoint(b) whether they share nothing. Each operator has a method form (union, intersection, difference, symmetric_difference) that also accepts any iterable, not only another set.
One habit matters: never rely on the order you see when a set is printed. It depends on hash values and insertion history and can differ between runs and between Python versions. When order matters for output, call sorted() on the set first, as every example here does.
Syntax
badges = {"B-204", "B-117"} # literal with items
empty = set() # {} would be an empty dict
from_list = set(["a", "b", "a"]) # duplicates collapse
badges.add("B-350")
badges.discard("B-999") # no error if absent
badges.remove("B-117") # KeyError if absent
"B-204" in badges
a | b, a & b, a - b, a ^ b # union, intersection, difference, symmetric difference
a <= b, a >= b, a.isdisjoint(b) # subset, superset, nothing in common
frozenset(["x", "y"]) # immutable set, usable as a dict keyCurly braces make a set only when they contain items. An empty pair of braces is a dictionary.
Removing duplicates and testing membership
A door scanner logs every badge swipe, including repeats. Converting the log to a set answers how many different badges were used.
scans = ["B-204", "B-117", "B-204", "B-350", "B-117", "B-204"]
unique = set(scans)
print(len(scans), "scans")
print(len(unique), "different badges")
print(sorted(unique))
print("B-350" in unique)
print("B-999" in unique)Output
6 scans 3 different badges ['B-117', 'B-204', 'B-350'] True False
set(scans) keeps one copy of each badge, so the length drops from 6 to 3. The set is printed through sorted(), which returns a list in a predictable order; printing the set directly would show the same three badges in an order you cannot count on. The two in tests are the operation sets are built for.
Comparing two groups with set algebra
Two shift rosters overlap. Each set operator answers a different scheduling question in one expression.
morning = {"Ines", "Kwame", "Priya", "Tomas"}
evening = {"Priya", "Ravi", "Tomas", "Yuki"}
print("both:", sorted(morning & evening))
print("anyone:", sorted(morning | evening))
print("morning only:", sorted(morning - evening))
print("one shift only:", sorted(morning ^ evening))
print(morning.isdisjoint(evening))
print({"Priya", "Tomas"} <= morning)Output
both: ['Priya', 'Tomas'] anyone: ['Ines', 'Kwame', 'Priya', 'Ravi', 'Tomas', 'Yuki'] morning only: ['Ines', 'Kwame'] one shift only: ['Ines', 'Kwame', 'Ravi', 'Yuki'] False True
Intersection finds people working both shifts, union everyone on either roster, difference the morning-only staff, and symmetric difference those on exactly one shift. isdisjoint is False because Priya and Tomas appear in both. The subset test <= confirms that both of them are on the morning roster. None of these needed a loop.
Building a set from input and removing members
Ingredients are read one per line. The set spots repeats as they arrive, and the two removal methods behave differently when a value is absent.
seen = set()
count = int(input())
for _ in range(count):
item = input().strip()
if item in seen:
print(f"{item} already listed")
else:
seen.add(item)
seen.discard("water") # absent: nothing happens
print(sorted(seen))
seen.remove("flour") # present: removed
print(sorted(seen))Input given to the program: 5 ↵ flour ↵ sugar ↵ flour ↵ butter ↵ sugar
Output
flour already listed sugar already listed ['butter', 'flour', 'sugar'] ['butter', 'sugar']
The in check before add is what lets the program report a repeat instead of silently ignoring it; add alone would also keep the set correct but say nothing. discard("water") is a no-op because water was never added, whereas remove on a missing value would have raised KeyError. Use remove when absence is a bug you want to notice and discard when it is normal.
Set operations with a = {1, 2, 3} and b = {3, 4}
| Expression | Meaning | Result |
|---|---|---|
| a | b | union: in either | {1, 2, 3, 4} |
| a & b | intersection: in both | {3} |
| a - b | difference: in a but not in b | {1, 2} |
| a ^ b | symmetric difference: in exactly one | {1, 2, 4} |
| a <= b | is a a subset of b? | False |
| a.isdisjoint(b) | do they share nothing? | False |
Common mistakes
Writing {} to make an empty set
Why it goes wrong:
{}is an empty dictionary. Braces only create a set when there is at least one item inside them.Fix: Use
set()for an empty set.Python · fixseen = set() print(type({}).__name__, type(seen).__name__) # dict setPrinting a set and expecting a particular order
Why it goes wrong: Sets are unordered. The order you see depends on hashing and insertion history, and it can change between runs and between Python versions, so code or a test that expects a specific printed order is fragile.
Fix: Sort before printing or comparing as a sequence:
sorted(items). Compare sets to sets with==, which ignores order.Adding a list or a dictionary to a set
Why it goes wrong: Set members must be hashable so they can be found by hash. A list can change after insertion, so its hash would go stale; Python refuses with
TypeError: unhashable type: 'list'.Fix: Convert to a tuple first:
points.add((3, 4))orpoints.add(tuple(pair)).Using remove() on a value that might be absent
Why it goes wrong:
removeraisesKeyErrorwhen the value is not in the set, which is easy to hit when the data comes from a file or a user.Fix: Use
discard()when absence is normal, or check withinfirst.Python · fixcart.discard("cheese") # silent if missing
Where you use this
The classic job for a set is "have I seen this before?". Reading a log file and collecting the distinct user IDs, walking a graph without revisiting nodes, or checking a submitted list of email addresses for duplicates are all one add per item followed by cheap membership tests. Without a set, the same task needs a list and a linear search on every check, which turns a fast script into a slow one once the input grows.
Set algebra answers comparison questions directly. Which permissions does this role have that the default role lacks: role - default. Which products appear in both catalogue files: catalogue_a & catalogue_b. Which required fields are missing from a submitted form: required - submitted. Each is one expression instead of a nested loop.
required = {"name", "email", "age"}
submitted = {"name", "age"}
missing = required - submitted
if missing:
print("missing fields:", sorted(missing))Key points
- A set stores unique, hashable values with no reliable order.
inon a set is fast regardless of size; on a list it scans every element.- Use
set()for an empty set;{}is a dictionary. add,discardandremovechange a set; onlydiscardtolerates absent values.- Combine sets with
|,&,-and^, or the equivalent methods, which accept any iterable. - Sort a set before printing or comparing so output is deterministic.
frozensetis an immutable set that can be a dictionary key or a member of another set.
Try it yourself
The first line lists the tool IDs checked out in the morning; the second lists the IDs returned in the evening. Print a sorted list of the IDs that were checked out but not returned.
out = set(input().split())
back = set(input().split())
# print the sorted IDs that are in out but not in back
print(sorted(out))t3 t9 t1 t7 ↵ t9 t1['t3', 't7']out = set(input().split())
back = set(input().split())
print(sorted(out - back))Practise this
Exercises for this lesson are in the Python practice set.
Related lessons
- Lists in PythonHow Python lists store ordered, changeable collections: creating them, indexing and slicing, adding and removing items, sorting, copying and looping over them.9 min
- Dictionaries in PythonHow Python dictionaries map keys to values: creating and updating entries, safe lookup with get, iterating items in insertion order, counting and grouping.9 min
- Tuples in PythonHow Python tuples work: building fixed records, unpacking values into names, returning several results from a function and using tuples as dictionary keys.8 min
- Comprehensions in PythonHow list, dict and set comprehensions build a collection in one expression, with filters, transformations and nested loops, and when a loop is clearer.8 min
Frequently asked questions
Are Python sets ordered?
No. A set has no positional order, and the order in which items print is not guaranteed to match insertion order or to stay the same between runs. If you need both uniqueness and order, use dict.fromkeys(items), since dictionaries preserve insertion order and drop duplicate keys, or keep a list for order alongside a set for membership.
When should I use a set instead of a list?
Use a set when the important operations are membership tests, removing duplicates or comparing two collections, and when order and duplicates do not matter. Use a list when you need positions, duplicates, sorting in place, or values that are not hashable.
What is the difference between remove and discard on a set?
Both delete one value. remove raises KeyError if the value is not in the set; discard does nothing in that case. Choose remove when absence would be a bug you want to notice, and discard when absence is an ordinary situation.
How this page was checked. Every program on it was run with CPython 3.11 at build time; runs in your browser on CPython 3.14 (Pyodide) by the publishing checks, and the output shown is what it printed. The Run buttons execute the same code on your device; nothing you type is sent anywhere.