Session 16 - Sets & Set Operations

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

What this lesson covers

The lesson, slide by slide

1. Sets

Title

Python Fundamentals - Session 16

Unordered collections of unique items

2. What you will be able to do

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. Build a set with {1, 2, 3} and know the empty set is set(), never {}.
  2. Add with .add, and remove safely with .discard (vs .remove).
  3. Test membership with in, and deduplicate a list with set(list).
  1. Combine sets with union |, intersection &, difference -, symmetric difference ^.
  2. Explain why set[0] fails and why only hashable items are allowed.
  3. Spot the four classic set traps before they bite you.

3. What survived from Session 15 - Tuples & Unpacking?

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.

4. What a Set Is

Section

Part 1

5. A set holds unique items, unordered

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.

6. Break it if you can: A set holds unique items, unordered

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.

7. Curly braces with commas make a set

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.

8. By analogy: Curly braces with commas make a set

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.

9. Restore the missing line: Build a set and check its type

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.

10. Build a set and check its type

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.

expressionoutput
print(colors){1, 2, 3}
print(type(colors))<class 'set'>

11. Fill in: output for Build a set and check its type

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.

expressionoutput
print(colors){1, 2, 3}
print(type(colors))<class 'set'>

12. A set is a bag of stickers

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.

13. Teach it back: A set is a bag of stickers

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

14. Sets silently drop duplicates and order

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.

15. Duplicates disappear

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 typedprint(s)len(s)
{3, 1, 2, 2, 1}{1, 2, 3}3

16. Inspect it line by line: Duplicates disappear

Error analysis

Annotate

Walk the callouts on Duplicates disappear. Each one is a place this is easy to get subtly wrong.

  • The two extra 1s and the extra 2 collapse into one each.
  • Verified by execution: only the three unique values survive, so len is 3.

17. Set vs list vs dict at a glance

Concept

You now know three containers. They split cleanly by what they promise about order and duplicates.

containerordered?duplicates?indexable?
list [ ]yesallowedyes (s[0])
dict { k: v }insertion orderunique keysby key (d[k])
set { }noneverno

18. What each one costs: Set vs list vs dict at a glance

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.

containerordered?duplicates?indexable?
list [ ]yesallowedyes (s[0])
dict { k: v }insertion orderunique keysby key (d[k])
set { }noneverno

19. The Empty Set Trap

Section

Part 2

20. Empty set is set(), not {}

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.

21. set() vs {}

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 writetype
set()<class 'set'>
{}<class 'dict'>

22. Draw the shape of it: set() vs {}

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.

23. Something is wrong here: using {} for an empty set

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.

24. Trap: using {} for an empty set

Trap

The 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 writetype
{}<class 'dict'>

The fix

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 writetype
set()<class 'set'>

25. Adding & Removing

Section

Part 3

26. .add puts one item in

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.

27. Predict the next row: Adding, including a duplicate

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}

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

28. Adding, including a duplicate

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.

stepset after
start{1, 2, 3}
s.add(4){1, 2, 3, 4}
s.add(2){1, 2, 3, 4}

29. Watch it run: Adding, including a duplicate

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?

  1. Step 1: step is start
  2. Step 2: step is s.add(4)
  3. Step 3: step is s.add(2)

30. What has to happen first: Full trace: building a set in a loop

Ranking

Put in order

Put the moves of Full trace: building a set in a loop into the order they have to happen.

  1. Start empty, add each roll
  2. Repeats add nothing
  3. Trace every pass

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.

31. Full trace: building a set in a loop

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.

rseen after addgrew?
2{2}yes
5{2, 5}yes
2{2, 5}no
6{2, 5, 6}yes
5{2, 5, 6}no

32. Watch it run: Full trace: building a set in a loop

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?

  1. Step 1: r is 2
  2. Step 2: r is 5
  3. Step 3: r is 2
  4. Step 4: r is 6
  5. Step 5: r is 5

33. .discard is safe; .remove is strict

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.

34. Take the definitions apart: set vs .discard vs .remove

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.

set
An unordered collection of unique, hashable items.; Duplicates are dropped automatically and there is no positional order.
.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.
b1
An unordered collection of unique, hashable items. Duplicates are dropped automatically and there is no positional order.
b2
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.

35. discard removes, and is safe when absent

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.

stepset after
start{1, 2, 3}
s.discard(2){1, 3}
s.discard(99){1, 3}

36. Watch it run: discard removes, and is safe when absent

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?

  1. Step 1: step is start
  2. Step 2: step is s.discard(2)
  3. Step 3: step is s.discard(99)

37. Something is wrong here: .remove on a missing item

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.

38. Trap: .remove on a missing item

Trap

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

callresult
s.remove(99)KeyError: 99

The fix

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.

callresult
s.discard(99){1, 2, 3}

39. Break it on purpose: .remove on a missing item

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.

40. Membership: in

Section

Part 4

41. in tests membership, fast

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.

42. Why membership is instant

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?'.

43. Membership checks

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.

expressionoutput
2 in sTrue
9 in sFalse
9 not in sTrue

44. Fill in: output for Membership checks

Comparison

Comparison matrix

From Membership checks: refill the output column from what you know. The rest of the table is as it appeared.

expressionoutput
2 in sTrue
9 in sFalse
9 not in sTrue

45. Deduplicating a List

Section

Part 5

46. set(list) removes duplicates

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().

47. Deduplicate a list

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

48. Count distinct values

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

scoresset(scores)distinct
[90, 85, 90, 100, 85]{90, 100, 85}3

49. Unordered: No Indexing

Section

Part 6

50. You cannot index a set

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.

51. Something is wrong here: indexing a set

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

52. Trap: indexing a set

Trap

The 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'.

callresult
s[0]TypeError: 'set' object is not subscriptable

The fix

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.

callresult
sorted(s)[1, 2, 3]
sorted(s)[0]1

53. Get order back with sorted()

Concept

sorted(s) returns a new list with the items in order. It does not change the set - it hands back an ordered copy.

54. Restore the missing line: Sort a set into a list

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.

55. Sort a set into a list

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.

expressionoutput
len(letters)3
sorted(letters)['a', 'b', 'n']

56. Only Hashable Items

Section

Part 7

57. Items must be hashable

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.

58. Term to definition: Session 16 - Sets & Set Operations

Matching

Match the pairs

Match each term to the definition this lesson gave it — not the one you would guess from the word.

  • t1. set
  • t2. .discard vs .remove
  • t3. hashable
  • d1. An unordered collection of unique, hashable items. Duplicates are dropped automatically and there is no positional order.
  • d2. 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.
  • d3. 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.

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.

59. Something is wrong here: putting a list in a set

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

60. Trap: putting a list in a set

Trap

The 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'.

callresult
s.add([3, 4])TypeError: unhashable type: 'list'

The fix

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.

callresult
s.add((3, 4)){1, 2, (3, 4)}

61. Which of these survive contact with Session 16 - Sets & Set Operations?

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.

Holds up
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).; Write items between { 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'.
Breaks
You want an empty set to fill up, so you reach for the braces.; You call .remove for an item that is not in the set.
sound
These are stated as this lesson states them — each one survives the edge cases Session 16 - Sets & Set Operations puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

62. Set Operations

Section

Part 8

63. Think in overlapping circles

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.

64. Union | : everything in either

Concept

a | b is the union: every item that is in a, in b, or both - with duplicates collapsed, like any set.

65. Union of two sets

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.

aba | b
{1, 2, 3}{3, 4, 5}{1, 2, 3, 4, 5}

66. Intersection & : only the shared

Concept

a & b is the intersection: only the items that are in both a and b. Everything else is dropped.

67. Intersection of two sets

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.

aba & b
{1, 2, 3}{3, 4, 5}{3}

68. Where the cost goes: Intersection of two sets

Cost model

Annotate

In Intersection of two sets, before reading the notes: mark where the time actually goes. Which line dominates?

  • 3 is the only item in a and b at the same time.
  • Verified by execution: just the single shared value.

69. Difference - : in a but not b

Concept

a - b is the difference: items in a with anything also in b removed. Order matters - b - a is different.

70. Teach it back: Difference - : in a but not b

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.

71. Difference both directions

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.

expressionoutput
a - b{1, 2}
b - a{4, 5}

72. Symmetric difference ^ : in one, not both

Concept

a ^ b is the symmetric difference: items in a or b but not in both. It is everything the two do not share.

73. By analogy: Symmetric difference ^ : in one, not both

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.

74. Symmetric difference

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

aba ^ b
{1, 2, 3}{3, 4, 5}{1, 2, 4, 5}

75. Draw the shape of it: Symmetric difference

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.

76. What has to happen first: All four operators at once

Ranking

Put in order

Put the moves of All four operators at once into the order they have to happen.

  1. Two attendance lists, four questions
  2. sorted() gives a stable, readable order
  3. Read every result

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.

77. All four operators at once

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.

operatormeaningoutput
monday | tuesdayanyone (count)4
monday & tuesdayboth days['Ben', 'Cara']
monday - tuesdayMonday only['Ana']
monday ^ tuesdayexactly one day['Ana', 'Dan']

78. Inspect it line by line: All four operators at once

Error analysis

Annotate

Walk the callouts on All four operators at once. Each one is a place this is easy to get subtly wrong.

  • Union = anyone who came; intersection = came both days; difference = Monday-only; symmetric = came exactly one day.
  • Sets are unordered, so sorting the results makes the output deterministic.
  • Verified by execution: four people total; Ben and Cara both days; only Ana on Monday; Ana and Dan came exactly one day.

79. Operators build a new set

Concept

|, &, -, and ^ each return a new set and leave the two originals untouched. You usually store the result in a new name.

80. Break it if you can: Operators build a new set

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.

81. Restore the missing line: The originals are unchanged

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.

82. The originals are unchanged

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.

namevalue after
c{3}
a{1, 2, 3}
b{3, 4, 5}

83. What each one costs: The originals are unchanged

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.

namevalue after
c{3}
a{1, 2, 3}
b{3, 4, 5}

84. Patterns & Checks

Section

Part 9

85. When to reach for a set

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.

86. Predict the next row: The four set operators

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

operatornamekeeps
a | bunionin either (or both)
a & bintersectionin both
a - bdifferencein a, not in b
a ^ bsymmetric differencein 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.

87. The four set operators

Pattern

Learn them as one family

Why: Each takes two sets and returns a new set; pick by which items you want to keep.

operatornamekeeps
a | bunionin either (or both)
a & bintersectionin both
a - bdifferencein a, not in b
a ^ bsymmetric differencein exactly one

88. Fill in: keeps for The four set operators

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.

operatornamekeeps
a | bunionin either (or both)
a & bintersectionin both
a - bdifferencein a, not in b
a ^ bsymmetric differencein exactly one

89. Check: the empty set

Check

Which type is this?

x = {}
print(type(x))
you writetype
{}?

Check your understanding

What does this print?

  • A. <class 'dict'> (correct)
  • B. <class 'set'>
  • C. set()
  • D. <class 'list'>

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.

Why B tempts people
Empty braces belong to dict; you only get a set from braces when there is at least one item inside.
Why C tempts people
set() is how you build an empty set, but it is not what {} produces - and type() prints <class ...>, not set().
Why D tempts people
Braces never make a list; a list uses square brackets [].

90. Check: duplicates and len

Check

Count the survivors.

s = {1, 1, 2, 3, 3}
print(len(s))
you typedlen(s)
{1, 1, 2, 3, 3}?

Check your understanding

What does this print?

  • A. 3 (correct)
  • B. 5
  • C. 2
  • D. Error - duplicate keys

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.

Why B tempts people
5 is how many you typed; the set collapses duplicates before len counts, so it is 3.
Why C tempts people
Only the 1 and 3 were duplicated; 2 still counts, giving three unique values.
Why D tempts people
Duplicates in a set literal are not an error - they are silently dropped.

91. Check: discard vs remove

Check

The item is not in the set.

s = {1, 2, 3}
s.discard(9)
print(s)
callresult
s.discard(9)?

Check your understanding

What does this print?

  • A. {1, 2, 3} (correct)
  • B. KeyError: 9
  • C. {1, 2, 3, 9}
  • D. None

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.

Why B tempts people
KeyError is what .remove(9) would raise; .discard is the safe version that stays quiet.
Why C tempts people
discard removes items, it never adds; 9 does not get inserted.
Why D tempts people
print(s) shows the set, not None; discard returns None but we are printing s.

92. Check: intersection

Check

What do they share?

print({1, 2, 3} & {2, 3, 4})
aba & b
{1, 2, 3}{2, 3, 4}?

Check your understanding

What does this print?

  • A. {2, 3} (correct)
  • B. {1, 2, 3, 4}
  • C. {1, 4}
  • D. {1}

Answer: A

Why: & keeps only items in both sets. 2 and 3 appear in each, so the intersection is {2, 3}. Verified by execution.

Why B tempts people
That is the union (|), which keeps everything in either set - not the intersection.
Why C tempts people
{1, 4} is the symmetric difference (^): items in exactly one set, the opposite of what & keeps.
Why D tempts people
{1} is the difference {1, 2, 3} - {2, 3, 4}; & keeps the shared items, not the leftover ones.

93. Check: indexing a set

Check

Grab the first item.

s = {10, 20, 30}
print(s[0])
callresult
s[0]?

Check your understanding

What happens?

  • A. TypeError: 'set' object is not subscriptable (correct)
  • B. 10
  • C. It prints the smallest item, 10
  • D. IndexError: set index out of range

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.

Why B tempts people
There is no 'first' item in a set, so s[0] never returns a value - it raises before printing.
Why C tempts people
Sets are not sorted and have no index; use sorted(s)[0] if you want the smallest.
Why D tempts people
The error is a TypeError (indexing is not supported at all), not an IndexError about range.

94. Check: symmetric difference

Check

Which items are in exactly one set?

print({1, 2, 3} ^ {2, 3, 4})
aba ^ b
{1, 2, 3}{2, 3, 4}?

Check your understanding

What does this print?

  • A. {1, 4} (correct)
  • B. {2, 3}
  • C. {1, 2, 3, 4}
  • D. {}

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.

Why B tempts people
{2, 3} is the intersection (&) - the shared items, which ^ removes rather than keeps.
Why C tempts people
That is the union (|). Symmetric difference excludes the items both sets share.
Why D tempts people
The sets do share 2 and 3, but 1 and 4 are unshared, so the result is not empty.

95. Check: hashable items

Check

Add a list to a set.

s = {1, 2}
s.add([3, 4])
callresult
s.add([3, 4])?

Check your understanding

What happens?

  • A. TypeError: unhashable type: 'list' (correct)
  • B. {1, 2, 3, 4}
  • C. {1, 2, [3, 4]}
  • D. It adds 3 and 4 separately

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.

Why B tempts people
add inserts its argument as one item, not several; and a list cannot be that item anyway.
Why C tempts people
A set cannot hold a list at all - it is unhashable, so nothing is stored and an error is raised.
Why D tempts people
add never splits its argument; to add 3 and 4 individually you would call add twice with numbers.

96. Connect it up: Session 16 - Sets & Set Operations

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.

97. What you can do now

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 writeIt 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 sfast membership test
set(mylist)the unique items of a list
a | b, a & b, a - b, a ^ bunion, 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.

Sources

  1. Python 3 Tutorial - Sets
  2. Python 3 Standard Types - set, frozenset
  3. Python 3 Glossary - hashable
  4. All snippets and error messages executed and copied from CPython 3.12. — Author verification run, 2026-07-15 (Python Fundamentals series, Session 16).

Want this taught 1-on-1? Alexander tutors Python Fundamentals — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108