BitBootcampBitBootcamp ← all explainers 🔄 Review ⚙ Settings

PY-01-010-P · Primer — Sets

The idea in one sentence

A set is an unordered collection that automatically throws away duplicates, so it holds each item at most once and answers "is this thing in here?" and "what do these two groups share?" almost instantly.

Why it exists

Some questions are really about membership, not about order or counting: has this user already been emailed? Which tags did both articles use? What items are in cart A but not cart B? You could answer these with a list, but a list keeps duplicates and has to scan item by item every time you ask "is X in here?", which gets painfully slow as the list grows. A set exists precisely for these jobs: it guarantees uniqueness for free, so adding something twice is simply a no-op, and it's built for lightning-fast membership checks because it stores items by their hash rather than in a row. It also comes with operators borrowed straight from the math of sets — union, intersection, difference — so comparing two groups of things becomes a single readable line instead of a nested loop. Reach for a set whenever you catch yourself thinking "I just need the distinct ones" or "what overlaps."

How it works

You write a set with curly braces around comma-separated items, {"alice", "bob"}, or build one from any sequence with set(...). Duplicates silently collapse to one, and the items come back in no particular order — a set has no indexes, so my_set[0] is an error. The star feature is the in operator, which checks membership in roughly constant time no matter how big the set is. And three operators do set-algebra directly: | is union (everything in either), & is intersection (only what's in both), and - is difference (in the left one but not the right).

inbox = {"alice", "bob", "alice", "carol", "bob"}
print(inbox)            # {'carol', 'bob', 'alice'}  <- duplicates gone, order varies
print(len(inbox))       # 3
print("alice" in inbox) # True   (fast membership test)
print("dave" in inbox)  # False

editors = {"alice", "bob"}
viewers = {"bob", "carol"}
print(editors | viewers)  # {'alice', 'bob', 'carol'}  union: in either
print(editors & viewers)  # {'bob'}                     intersection: in both
print(editors - viewers)  # {'alice'}                   difference: editors only

Because printing a set shows items in an arbitrary order, focus on which items appear, not the sequence.

The one gotcha

{} is an empty dictionary, not an empty set — to make an empty set you must write set(). This trips up nearly everyone, because the curly-brace shorthand that works for a populated set ({"a", "b"}) quietly means something else when it's empty. The braces were claimed by dictionaries first, so Python resolves the ambiguous {} in the dict's favor:

seen = set()        # correct: an empty set, ready to collect unique items
print(type(seen))   # <class 'set'>
print(type({}))     # <class 'dict'>   <- NOT what you wanted!

A second thing to remember: sets can only hold hashable (immutable) items, so strings, numbers, and tuples are fine, but you can't put a list or another set inside a set. When in doubt, start with set() and .add() items one at a time.

See it — choosing a set and running set-algebra

flowchart TD
    A["I have a bunch<br/>of items"] --> B{"Do I care about<br/>order or duplicates?"}
    B -->|"Need order or<br/>counts kept"| C["Use a list"]
    B -->|"Just want the<br/>distinct ones"| D["Wrap in set(...)<br/>duplicates collapse"]
    D --> E{"What am I<br/>asking?"}
    E -->|"Is X present?"| F["x in myset<br/>fast membership"]
    E -->|"In either group?"| G["a | b  (union)"]
    E -->|"In both groups?"| H["a & b  (intersection)"]
    E -->|"Only in a, not b?"| I["a - b  (difference)"]

The one-line takeaway

A set stores each item once and unordered, making it the go-to tool for deduplication, instant in checks, and comparing groups with |, &, and -.

Where you'll meet it

The single most common place you'll meet sets is deduplication: list(set(items)) is the classic one-liner for "give me the unique values," whether that's distinct email addresses, tags, IP addresses, or words in a document. You'll use in on a set to track "have I already processed this?" inside loops — a seen = set() that you .add() to is a staple of crawlers, validators, and anything that must avoid repeats. The algebra operators show up whenever two groups meet: which permissions does a user have that a role grants (user_perms & role_perms), which required fields are missing from a form (required - provided), which customers appear in both this month and last (this_month & last_month). And once you've felt how much faster and clearer a set is than a hand-written nested loop, you'll start spotting set-shaped problems everywhere — that instinct is one of the quiet marks of a fluent Python programmer.

Named concepts: unordered unique collections, membership testing, union/intersection/difference, deduplication use cases