Python · Intermediate

Sets in Python

8 min readUpdated September 24, 2026Every example verified

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

 Python · 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 key

Curly 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.

 Python
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.

 Python
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.

 Python
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: 5floursugarflourbuttersugar

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}

ExpressionMeaningResult
a | bunion: in either{1, 2, 3, 4}
a & bintersection: in both{3}
a - bdifference: in a but not in b{1, 2}
a ^ bsymmetric difference: in exactly one{1, 2, 4}
a <= bis 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 · fix
    seen = set()
    print(type({}).__name__, type(seen).__name__)   # dict set
  • Printing 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)) or points.add(tuple(pair)).

  • Using remove() on a value that might be absent

    Why it goes wrong: remove raises KeyError when 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 with in first.

     Python · fix
    cart.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.

 Python · in practice
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.
  • in on a set is fast regardless of size; on a list it scans every element.
  • Use set() for an empty set; {} is a dictionary.
  • add, discard and remove change a set; only discard tolerates 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.
  • frozenset is 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.

Your program
out = set(input().split())
back = set(input().split())
# print the sorted IDs that are in out but not in back
print(sorted(out))
Input the program receives: t3 t9 t1 t7 ↵ t9 t1
Expected output: ['t3', 't7']

Practise this

Exercises for this lesson are in the Python practice set.

Open the Python playground

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.

Progress is stored only in this browser.

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.