Session 16 of the Python Fundamentals series, covered in depth. Sets are unordered collections of unique items, and the session covers building them with {1, 2, 3} and with set(), why the empty set is set() and never {}, adding with .add, removing safely with .discard rather than .remove, fast membership testing with in, deduplicating a list with set(), and the four combining operators: union |, intersection &, difference -, and symmetric difference ^. The traps are that {} is an empty dictionary and not a set, that sets silently drop duplicates and order, that sets cannot be indexed, so set[0] raises a TypeError, that only hashable items may be stored, and that .remove on a missing item raises a KeyError while .discard is safe. Every snippet and error message was executed and copied verbatim from CPython 3.12.
Subject: Python Fundamentals · 97 slides · code lesson
Open the interactive version of this deck · Homework for this lesson
Title
Python Fundamentals - Session 16
Unordered collections of unique items
Objectives
You know lists and dicts. A set is a third container: no duplicates, no order, and lightning-fast membership. By the end you can:
{1, 2, 3} and know the empty set is set(), never {}..add, and remove safely with .discard (vs .remove).in, and deduplicate a list with set(list).|, intersection &, difference -, symmetric difference ^.set[0] fails and why only hashable items are allowed.Warm-up
Discussion prompt
Before we open Session 16 - Sets & Set Operations: without looking back, what was the main idea of Session 15 - Tuples & Unpacking, and what could you do by the end of it that you could not do before?
Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.
Answer:
That session covers immutable ordered tuples: creating them, the trailing-comma rule for a single element, packing and unpacking, one-line swaps, unpacking in loops, returning several values, and star unpacking. It also covers immutability as the basis for safe records and dictionary keys, and when to choose a tuple over a list.
Section
Part 1
Concept
A set is a container like a list, with two twists: every item is unique (no duplicates), and there is no order (no first, no last, no index).
set — An unordered collection of unique, hashable items. Duplicates are dropped automatically and there is no positional order.
Counterexample
Discussion prompt
A set is a container like a list, with two twists: every item is unique (no duplicates), and there is no order (no first, no last, no index).
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Concept
Write items between { and }, separated by commas: {1, 2, 3}. That is a set literal.
It looks like a dict, but a dict has key: value pairs. A set is just values - no colons.
Analogy
Discussion prompt
Explain Curly braces with commas make a set by analogy to something with no Python Fundamentals in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
Write items between { and }, separated by commas: {1, 2, 3}. That is a set literal.
Fill the middle
Fill in the blanks
From Build a set and check its type — one line has had its right-hand side removed. Put it back.
colors = {1, 2, 3}
print(colors)
print(type(colors))
Why: colors is what everything below it consumes, so the wrong expression here fails later and somewhere else. Comma-separated values in {} with no colons make a set, not a dict.
Worked example
colors = {1, 2, 3}
print(colors)
print(type(colors))The braces build a set of three items
Why: Comma-separated values in {} with no colons make a set, not a dict.
Read the output
Why: Verified by execution: the set prints, and type confirms it is a set.
| expression | output |
|---|---|
| print(colors) | {1, 2, 3} |
| print(type(colors)) | <class 'set'> |
Comparison
Comparison matrix
From Build a set and check its type: refill the output column from what you know. The rest of the table is as it appeared.
| expression | output |
|---|---|
| print(colors) | {1, 2, 3} |
| print(type(colors)) | <class 'set'> |
Intuition
Picture a bag you drop labeled stickers into. You can ask 'is the red sticker in here?' and dump the bag out - but there is no 'sticker number 3'.
Drop a second red sticker in and nothing changes: the bag already had red. That is uniqueness. And the stickers float freely - that is unordered.
Explain it
Discussion prompt
Explain A set is a bag of stickers to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
Picture a bag you drop labeled stickers into. You can ask 'is the red sticker in here?' and dump the bag out - but there is no 'sticker number 3'.
Concept
When you build a set, Python throws away repeats and does not keep the order you typed. What you get back is the unique items, arranged however Python likes.
Worked example
s = {3, 1, 2, 2, 1}
print(s)
print(len(s))Five items typed, but two are repeats
Why: The two extra 1s and the extra 2 collapse into one each.
Read the output
Why: Verified by execution: only the three unique values survive, so len is 3.
| you typed | print(s) | len(s) |
|---|---|---|
| {3, 1, 2, 2, 1} | {1, 2, 3} | 3 |
Error analysis
Annotate
Walk the callouts on Duplicates disappear. Each one is a place this is easy to get subtly wrong.
Concept
You now know three containers. They split cleanly by what they promise about order and duplicates.
| container | ordered? | duplicates? | indexable? |
|---|---|---|---|
| list [ ] | yes | allowed | yes (s[0]) |
| dict { k: v } | insertion order | unique keys | by key (d[k]) |
| set { } | no | never | no |
Trade off
Comparison matrix
From Set vs list vs dict at a glance: every row here is a choice with a cost. Fill the ordered? column, then say which row you would actually pick and what you give up for it.
| container | ordered? | duplicates? | indexable? |
|---|---|---|---|
| list [ ] | yes | allowed | yes (s[0]) |
| dict { k: v } | insertion order | unique keys | by key (d[k]) |
| set { } | no | never | no |
Section
Part 2
Concept
There is one gotcha up front: {} is an empty dict, not an empty set. Python gave the braces to dicts first.
To make an empty set you must call set(). You only get set-braces once there is at least one item inside.
Worked example
empty = set()
print(type(empty))
d = {}
print(type(d))set() is the empty set
Why: Calling set() with no arguments gives you an empty set object.
{} is an empty dict
Why: Verified by execution: {} builds a dict, so its type is dict, not set.
| you write | type |
|---|---|
| set() | <class 'set'> |
| {} | <class 'dict'> |
Blank canvas
Draw it
Draw what set() vs {} just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Anomaly
Predict first
A student writes this, and it looks reasonable:
You want an empty set to fill up, so you reach for the braces.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: You silently made an empty dict.
Call set() for an empty set.
Why: You silently made an empty dict. Later .add would fail and set operators would misbehave - all because of two braces.
Trap
You want an empty set to fill up, so you reach for the braces.
s = {}
print(type(s)){} is a dict, not a set
Why: You silently made an empty dict. Later .add would fail and set operators would misbehave - all because of two braces.
| you write | type |
|---|---|
| {} | <class 'dict'> |
Call set() for an empty set.
s = set()
print(type(s))set() is genuinely a set
Why: Real output: <class 'set'>. Rule of thumb: empty set is always set(); {} means empty dict.
| you write | type |
|---|---|
| set() | <class 'set'> |
Section
Part 3
Concept
s.add(item) inserts one item into the set. If it is already there, nothing happens - the set stays the same.
.add changes the set in place and returns None; you do not reassign it.
Pattern
Predict first
The table runs: start | {1, 2, 3} · s.add(4) | {1, 2, 3, 4}
In Adding, including a duplicate, given the rows so far: what is the next one — the row where step is s.add(2)?
Correct: s.add(2) | {1, 2, 3, 4}
| step | set after |
|---|---|
| start | {1, 2, 3} |
| s.add(4) | {1, 2, 3, 4} |
| s.add(2) | {1, 2, 3, 4} |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. 4 was not present, so the set grows to four items.
Worked example
s = {1, 2, 3}
s.add(4)
print(s)
s.add(2)
print(s)add(4) inserts a new item
Why: 4 was not present, so the set grows to four items.
add(2) does nothing - 2 is already in
Why: Verified by execution: the second add is a no-op, the set is unchanged.
| step | set after |
|---|---|
| start | {1, 2, 3} |
| s.add(4) | {1, 2, 3, 4} |
| s.add(2) | {1, 2, 3, 4} |
Pattern
Step through it
Step through Adding, including a duplicate one row at a time. What is driving the change, and what would the row after the last one be?
Ranking
Put in order
Put the moves of Full trace: building a set in a loop into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. seen begins as set(); each pass adds one roll.
Worked example
rolls = [2, 5, 2, 6, 5]
seen = set()
for r in rolls:
seen.add(r)
print(r, seen)
print("unique:", len(seen))Start empty, add each roll
Why: seen begins as set(); each pass adds one roll.
Repeats add nothing
Why: The second 2 and second 5 are already in seen, so those passes leave it unchanged.
Trace every pass
Why: Verified by execution: three unique values remain, so len(seen) is 3.
| r | seen after add | grew? |
|---|---|---|
| 2 | {2} | yes |
| 5 | {2, 5} | yes |
| 2 | {2, 5} | no |
| 6 | {2, 5, 6} | yes |
| 5 | {2, 5, 6} | no |
Pattern
Step through it
Step through Full trace: building a set in a loop one row at a time. What is driving the change, and what would the row after the last one be?
Concept
Both take an item out. .discard(x) removes it if present and does nothing if not. .remove(x) removes it, but raises KeyError if it is missing.
.discard vs .remove — Use .discard when the item may or may not be there (safe). Use .remove only when you are sure it exists, or want the error if it does not.
Definition probe
Sort into buckets
Every line below is part of the definition of set or of .discard vs .remove — one or the other, never both. Put each where it belongs.
Worked example
s = {1, 2, 3}
s.discard(2)
print(s)
s.discard(99)
print(s)discard(2) removes the 2
Why: 2 is present, so it comes out.
discard(99) does nothing
Why: Verified by execution: 99 was never there, so discard quietly leaves the set alone - no error.
| step | set after |
|---|---|
| start | {1, 2, 3} |
| s.discard(2) | {1, 3} |
| s.discard(99) | {1, 3} |
Pattern
Step through it
Step through discard removes, and is safe when absent one row at a time. What is driving the change, and what would the row after the last one be?
Anomaly
Predict first
A student writes this, and it looks reasonable:
You call .remove for an item that is not in the set.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: 99 is not in the set, and .remove insists the item exists - so it crashes with the missing key.
Use .discard when the item might be missing.
Why: 99 is not in the set, and .remove insists the item exists - so it crashes with the missing key.
Trap
You call .remove for an item that is not in the set.
s = {1, 2, 3}
s.remove(99)remove(99) raises KeyError
Why: 99 is not in the set, and .remove insists the item exists - so it crashes with the missing key.
| call | result |
|---|---|
| s.remove(99) | KeyError: 99 |
Use .discard when the item might be missing.
s = {1, 2, 3}
s.discard(99)
print(s)discard(99) is a no-op
Why: Real output: {1, 2, 3}. discard never raises for a missing item, so it is the safe default.
| call | result |
|---|---|
| s.discard(99) | {1, 2, 3} |
Break the constraint
Discussion prompt
The rule this trap just fixed:
Real output: {1, 2, 3}. discard never raises for a missing item, so it is the safe default.
Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?
Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.
Answer:
99 is not in the set, and .remove insists the item exists - so it crashes with the missing key.
Section
Part 4
Concept
item in s gives True or False. not in is its opposite. This is the single most common thing sets are used for.
It is also fast: a set finds an item almost instantly, no matter how big it is - much faster than scanning a list.
Intuition
A list checks membership by walking item by item until it finds a match - slow if the list is long.
A set instead computes a hash of the item and jumps straight to where it would be stored. It does not scan; it looks up. That is why sets are the right tool for 'have I seen this before?'.
Worked example
s = {1, 2, 3}
print(2 in s)
print(9 in s)
print(9 not in s)2 is in the set
Why: 2 was one of the items, so the test is True.
9 is not
Why: Verified by execution: 9 in s is False, and 9 not in s flips that to True.
| expression | output |
|---|---|
| 2 in s | True |
| 9 in s | False |
| 9 not in s | True |
Comparison
Comparison matrix
From Membership checks: refill the output column from what you know. The rest of the table is as it appeared.
| expression | output |
|---|---|
| 2 in s | True |
| 9 in s | False |
| 9 not in s | True |
Section
Part 5
Concept
Pass any list (or string, or other iterable) to set() and you get a set of its unique items. This is the go-to way to dedupe.
Remember the price: you lose order and duplicates. If you need order back, wrap it in list() or sorted().
Worked example
nums = [3, 1, 2, 3, 1, 1]
unique = set(nums)
print(unique)
print(len(unique))set(nums) keeps one of each
Why: The repeated 3s and 1s collapse; three distinct values remain.
Read the output
Why: Verified by execution: six items in, three unique out.
| list (6 items) | set(nums) | len |
|---|---|---|
| [3, 1, 2, 3, 1, 1] | {1, 2, 3} | 3 |
Worked example
scores = [90, 85, 90, 100, 85]
print(len(set(scores)))len(set(...)) counts distinct values
Why: Wrapping in a set drops repeats, then len counts what is left.
Read the output
Why: Verified by execution: five scores, but only three distinct ones (90, 85, 100).
| scores | set(scores) | distinct |
|---|---|---|
| [90, 85, 90, 100, 85] | {90, 100, 85} | 3 |
Section
Part 6
Concept
Because a set has no order, there is no s[0]. Trying it raises TypeError: 'set' object is not subscriptable.
This is the price of uniqueness and speed: you trade away positions. If you need a position, you want a list.
Anomaly
Predict first
A student writes this, and it looks reasonable:
You treat the set like a list and grab item zero.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: A set has no positions, so subscripting it is meaningless - Python refuses with 'not subscriptable'.
Loop over it, test with in, or sort it into a list first.
Why: A set has no positions, so subscripting it is meaningless - Python refuses with 'not subscriptable'.
Trap
You treat the set like a list and grab item zero.
s = {1, 2, 3}
print(s[0])s[0] raises TypeError
Why: A set has no positions, so subscripting it is meaningless - Python refuses with 'not subscriptable'.
| call | result |
|---|---|
| s[0] | TypeError: 'set' object is not subscriptable |
Loop over it, test with in, or sort it into a list first.
s = {1, 2, 3}
print(sorted(s)[0])sorted(s) makes an ordered list
Why: Real output: 1. sorted returns a list, which does support [0]. Reach for a list when you need order or position.
| call | result |
|---|---|
| sorted(s) | [1, 2, 3] |
| sorted(s)[0] | 1 |
Concept
sorted(s) returns a new list with the items in order. It does not change the set - it hands back an ordered copy.
Fill the middle
Fill in the blanks
From Sort a set into a list — one line has had its right-hand side removed. Put it back.
letters = set("banana")
print(len(letters))
print(sorted(letters))
Why: letters is what everything below it consumes, so the wrong expression here fails later and somewhere else. The string's characters become items; repeats of a and n collapse.
Worked example
letters = set("banana")
print(len(letters))
print(sorted(letters))set("banana") keeps unique letters
Why: The string's characters become items; repeats of a and n collapse.
sorted gives an ordered list
Why: Verified by execution: three unique letters, alphabetized.
| expression | output |
|---|---|
| len(letters) | 3 |
| sorted(letters) | ['a', 'b', 'n'] |
Section
Part 7
Concept
A set can only hold hashable items: numbers, strings, tuples, booleans. Anything you cannot change in place.
hashable — An item with a fixed hash value that does not change over its life. Lists and dicts are mutable, so they are unhashable and cannot go in a set.
Matching
Match the pairs
Match each term to the definition this lesson gave it — not the one you would guess from the word.
Why: These are the working definitions of set, .discard vs .remove, hashable as Session 16 - Sets & Set Operations uses them. Pairing them correctly is the test of whether you could state each one with the slide switched off.
Anomaly
Predict first
A student writes this, and it looks reasonable:
You try to add a list as one item.
It is wrong. Say what breaks — and say it before you turn the page.
Correct: A list can change, so it has no stable hash - the set cannot store it and refuses with 'unhashable type'.
Use a tuple (unchangeable), which is hashable.
Why: A list can change, so it has no stable hash - the set cannot store it and refuses with 'unhashable type'.
Trap
You try to add a list as one item.
s = {1, 2}
s.add([3, 4])Adding a list raises TypeError
Why: A list can change, so it has no stable hash - the set cannot store it and refuses with 'unhashable type'.
| call | result |
|---|---|
| s.add([3, 4]) | TypeError: unhashable type: 'list' |
Use a tuple (unchangeable), which is hashable.
s = {1, 2}
s.add((3, 4))
print(s)A tuple is hashable
Why: Real output: {1, 2, (3, 4)}. Since a tuple cannot change, it has a stable hash and fits in the set.
| call | result |
|---|---|
| s.add((3, 4)) | {1, 2, (3, 4)} |
Two truths and a lie
Sort into buckets
Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.
{ and }, separated by commas: {1, 2, 3}. That is a set literal.; Picture a bag you drop labeled stickers into. You can ask 'is the red sticker in here?' and dump the bag out - but there is no 'sticker number 3'.Section
Part 8
Intuition
Picture two circles that overlap. The overlap is what the sets share; the outer crescents are what each has alone.
Union is both whole circles, intersection is the overlap, difference is one crescent, and symmetric difference is both crescents but not the overlap. The four operators are just names for these regions.
Concept
a | b is the union: every item that is in a, in b, or both - with duplicates collapsed, like any set.
Worked example
a = {1, 2, 3}
b = {3, 4, 5}
print(a | b)Pool both sets together
Why: 1,2,3 from a and 3,4,5 from b; the shared 3 appears once.
Read the output
Why: Verified by execution: five distinct items across both sets.
| a | b | a | b |
|---|---|---|
| {1, 2, 3} | {3, 4, 5} | {1, 2, 3, 4, 5} |
Concept
a & b is the intersection: only the items that are in both a and b. Everything else is dropped.
Worked example
a = {1, 2, 3}
b = {3, 4, 5}
print(a & b)Keep only what both share
Why: 3 is the only item in a and b at the same time.
Read the output
Why: Verified by execution: just the single shared value.
| a | b | a & b |
|---|---|---|
| {1, 2, 3} | {3, 4, 5} | {3} |
Cost model
Annotate
In Intersection of two sets, before reading the notes: mark where the time actually goes. Which line dominates?
Concept
a - b is the difference: items in a with anything also in b removed. Order matters - b - a is different.
Explain it
Discussion prompt
Explain Difference - : in a but not b to a student a year behind you. No notation, no jargon they have not met — and it still has to be true.
Hint: If your explanation needs a symbol they have never seen, you are describing the notation rather than the idea.
Answer:
a - b is the difference: items in a with anything also in b removed. Order matters - b - a is different.
Worked example
a = {1, 2, 3}
b = {3, 4, 5}
print(a - b)
print(b - a)a - b strips out b's items
Why: From a, remove 3 (it is in b); 1 and 2 remain.
b - a is the mirror
Why: Verified by execution: direction matters - the two results differ.
| expression | output |
|---|---|
| a - b | {1, 2} |
| b - a | {4, 5} |
Concept
a ^ b is the symmetric difference: items in a or b but not in both. It is everything the two do not share.
Analogy
Discussion prompt
Explain Symmetric difference ^ : in one, not both by analogy to something with no Python Fundamentals in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.
Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.
Answer:
a ^ b is the symmetric difference: items in a or b but not in both. It is everything the two do not share.
Worked example
a = {1, 2, 3}
b = {3, 4, 5}
print(a ^ b)Drop the shared item, keep the rest
Why: 3 is in both, so it goes; 1,2 and 4,5 remain.
Read the output
Why: Verified by execution: everything except the common 3. Note (a ^ b) equals (a - b) | (b - a).
| a | b | a ^ b |
|---|---|---|
| {1, 2, 3} | {3, 4, 5} | {1, 2, 4, 5} |
Blank canvas
Draw it
Draw what Symmetric difference just did — the shape of it, not the line-by-line working. One picture, labels only where you need them. Then check it against the steps: anything you could not draw is a step you followed rather than understood.
Ranking
Put in order
Put the moves of All four operators at once into the order they have to happen.
Why: These are the moves of the worked example in the order it makes them, and each one is set up by the one before it. Union = anyone who came; intersection = came both days; difference = Monday-only; symmetric = came exactly one day.
Worked example
monday = {"Ana", "Ben", "Cara"}
tuesday = {"Ben", "Cara", "Dan"}
print(len(monday | tuesday))
print(sorted(monday & tuesday))
print(sorted(monday - tuesday))
print(sorted(monday ^ tuesday))Two attendance lists, four questions
Why: Union = anyone who came; intersection = came both days; difference = Monday-only; symmetric = came exactly one day.
sorted() gives a stable, readable order
Why: Sets are unordered, so sorting the results makes the output deterministic.
Read every result
Why: Verified by execution: four people total; Ben and Cara both days; only Ana on Monday; Ana and Dan came exactly one day.
| operator | meaning | output |
|---|---|---|
| monday | tuesday | anyone (count) | 4 |
| monday & tuesday | both days | ['Ben', 'Cara'] |
| monday - tuesday | Monday only | ['Ana'] |
| monday ^ tuesday | exactly one day | ['Ana', 'Dan'] |
Error analysis
Annotate
Walk the callouts on All four operators at once. Each one is a place this is easy to get subtly wrong.
Concept
|, &, -, and ^ each return a new set and leave the two originals untouched. You usually store the result in a new name.
Counterexample
Discussion prompt
|, &, -, and ^ each return a new set and leave the two originals untouched. You usually store the result in a new name.
That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.
Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.
Fill the middle
Fill in the blanks
From The originals are unchanged — one line has had its right-hand side removed. Put it back.
a = {3, 4, 5}
b = ___
c = a & b
print(c)
print(a)
print(b)
Why: b is what everything below it consumes, so the wrong expression here fails later and somewhere else. The intersection is computed and stored in c; a and b are only read, not modified.
Worked example
a = {1, 2, 3}
b = {3, 4, 5}
c = a & b
print(c)
print(a)
print(b)a & b makes a new set c
Why: The intersection is computed and stored in c; a and b are only read, not modified.
a and b still hold their originals
Why: Verified by execution: c is the shared item, while a and b print exactly as before.
| name | value after |
|---|---|
| c | {3} |
| a | {1, 2, 3} |
| b | {3, 4, 5} |
Trade off
Comparison matrix
From The originals are unchanged: every row here is a choice with a cost. Fill the value after column, then say which row you would actually pick and what you give up for it.
| name | value after |
|---|---|
| c | {3} |
| a | {1, 2, 3} |
| b | {3, 4, 5} |
Section
Part 9
Pattern
1. Need uniqueness? Use a set
Why: Sets drop duplicates automatically - the cleanest way to dedupe a list is set(list).
2. Asking 'is X in here?' a lot? Use a set
Why: Membership with in is near-instant on a set, far faster than scanning a list.
3. Need order or positions? Use a list instead
Why: Sets have no index and no order; if s[0] matters, a set is the wrong tool.
4. Empty one? set() - and remove safely with .discard
Why: {} is a dict; .discard never raises, .remove raises KeyError when the item is missing.
Pattern
Predict first
The table runs: a | b | union | in either (or both) · a & b | intersection | in both · a - b | difference | in a, not in b
In The four set operators, given the rows so far: what is the next one — the row where operator is a ^ b?
Correct: a ^ b | symmetric difference | in exactly one
| operator | name | keeps |
|---|---|---|
| a | b | union | in either (or both) |
| a & b | intersection | in both |
| a - b | difference | in a, not in b |
| a ^ b | symmetric difference | in exactly one |
Why: The relationship between the columns, not the individual numbers, is what generates the next row. Each takes two sets and returns a new set; pick by which items you want to keep.
Pattern
Learn them as one family
Why: Each takes two sets and returns a new set; pick by which items you want to keep.
| operator | name | keeps |
|---|---|---|
| a | b | union | in either (or both) |
| a & b | intersection | in both |
| a - b | difference | in a, not in b |
| a ^ b | symmetric difference | in exactly one |
Comparison
Comparison matrix
From The four set operators: refill the keeps column from what you know. The rest of the table is as it appeared.
| operator | name | keeps |
|---|---|---|
| a | b | union | in either (or both) |
| a & b | intersection | in both |
| a - b | difference | in a, not in b |
| a ^ b | symmetric difference | in exactly one |
Check
Which type is this?
x = {}
print(type(x))| you write | type |
|---|---|
| {} | ? |
Check your understanding
What does this print?
Answer: A
Why: {} is an empty dict, not an empty set, so type(x) is dict. For an empty set you must call set(). Verified by execution.
Check
Count the survivors.
s = {1, 1, 2, 3, 3}
print(len(s))| you typed | len(s) |
|---|---|
| {1, 1, 2, 3, 3} | ? |
Check your understanding
What does this print?
Answer: A
Why: The set drops the repeated 1 and 3, leaving the unique values 1, 2, 3, so len is 3. Verified by execution.
Check
The item is not in the set.
s = {1, 2, 3}
s.discard(9)
print(s)| call | result |
|---|---|
| s.discard(9) | ? |
Check your understanding
What does this print?
Answer: A
Why: discard on a missing item does nothing and never raises, so the set prints unchanged as {1, 2, 3}. Verified by execution.
Check
What do they share?
print({1, 2, 3} & {2, 3, 4})| a | b | a & b |
|---|---|---|
| {1, 2, 3} | {2, 3, 4} | ? |
Check your understanding
What does this print?
Answer: A
Why: & keeps only items in both sets. 2 and 3 appear in each, so the intersection is {2, 3}. Verified by execution.
Check
Grab the first item.
s = {10, 20, 30}
print(s[0])| call | result |
|---|---|
| s[0] | ? |
Check your understanding
What happens?
Answer: A
Why: A set has no order or positions, so s[0] is not allowed and Python raises TypeError: 'set' object is not subscriptable. Verified by execution.
Check
Which items are in exactly one set?
print({1, 2, 3} ^ {2, 3, 4})| a | b | a ^ b |
|---|---|---|
| {1, 2, 3} | {2, 3, 4} | ? |
Check your understanding
What does this print?
Answer: A
Why: ^ keeps items in exactly one set. 2 and 3 are in both (dropped); 1 and 4 are each in only one, giving {1, 4}. Verified by execution.
Check
Add a list to a set.
s = {1, 2}
s.add([3, 4])| call | result |
|---|---|
| s.add([3, 4]) | ? |
Check your understanding
What happens?
Answer: A
Why: A list is mutable, so it has no stable hash and cannot be a set item; .add raises TypeError: unhashable type: 'list'. Verified by execution.
Connect it up
Draw it
One page, no notation unless you need it: draw how these connect — What a Set Is · The Empty Set Trap · Adding & Removing · Membership: in · Deduplicating a List · Unordered: No Indexing. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.
Recap
A set is an unordered collection of unique, hashable items. Build it with {1, 2, 3} - but the empty set is set(), never {}.
| You write | It means |
|---|---|
| {1, 2, 3} | a set of three unique items |
| set() | an empty set ({} is a dict) |
| s.add(x) / s.discard(x) | insert x / remove x safely |
| x in s | fast membership test |
| set(mylist) | the unique items of a list |
| a | b, a & b, a - b, a ^ b | union, intersection, difference, symmetric diff |
Reach for a set when you need uniqueness or fast membership; reach for a list when order or position matters. Next session we put containers to work together.
Want this taught 1-on-1? Alexander tutors Python Fundamentals — $55/session, free consultation.