Intuition What this page is
The parent note told you what sets and their four operations are. This page throws every kind of input at them — messy duplicates, empty sets, sets that fully contain each other, sets that share nothing, unhashable troublemakers, and a real word problem. By the end you should be unable to imagine a set situation you haven't already seen solved.
We use only the vocabulary the parent built: a set is an unordered, unique, hashable collection; the four operations are union | (OR), intersection & (AND), difference - (A not B), and symmetric difference ^ (exactly one). We lean on Boolean Logic — AND, OR, XOR and Hashing and Hashable Types underneath.
Definition The triangle symbol
△
Throughout this page we write A △ B as shorthand for symmetric difference — the same thing A ^ B computes: "elements in exactly one of A or B." Read the triangle as "either-or, not both." We use it only because it makes the algebra identities shorter to write; anywhere you see △ , mentally replace it with ^.
Before solving anything, let us enumerate every distinct situation a set problem can be. Each row is a "cell" — a case class that behaves differently. Every worked example below is tagged with the cell it covers, and together they fill the whole table.
Cell
Scenario class
What makes it its own case
Covered by
C1
Duplicates & unhashable inputs
uniqueness collapse; list/dict rejected
Ex 1
C2
Empty set as an operand
∅ is the "identity/zero" of each op
Ex 2
C3
Disjoint sets (share nothing)
intersection empties out
Ex 3
C4
Subset / superset (one inside other)
one difference empties, other doesn't
Ex 4
C5
Equal sets
every op degenerates predictably
Ex 5
C6
Asymmetry of -
A-B ≠ B-A; sign of "who keeps what"
Ex 6
C7
^ reconstruction from ∪ and ∩
the identity, checked numerically
Ex 7
C8
Mutation vs. return
update changes A; union doesn't
Ex 8
C9
Real-world word problem
translate English → set algebra
Ex 9
C10
Exam twist / chained ops
precedence, <=, order-of-evaluation
Ex 10
What you are looking at (Figure s01): four small Venn diagrams in a 2×2 grid, each two overlapping black circles labelled A (left) and B (right). The region relevant to each operation is shaded red . Top-left "Union A | B" shades all three regions (both crescents plus the middle lens). Top-right "Intersection A & B" shades only the lens where the circles overlap. Bottom-left "Difference A - B" shades only the left crescent — A's part outside B. Bottom-right "Sym.diff A ^ B" shades both crescents but leaves the lens white . This is our map : every example below is literally "which of these red regions survive?"
Worked example 1 — What survives the collapse?
data = [ 4 , 4 , 4 , 1 , 1 , 9 ]
s = set (data)
print ( len (s)) # Forecast: ___
bad = {[ 1 , 2 ], 3 } # Forecast: what happens?
ok = {( 1 , 2 ), 3 } # Forecast: ___
Forecast: guess len(s), guess whether bad runs, guess ok.
Step 1 — collapse duplicates. set(data) hashes each element and keeps one per distinct value: {1, 4, 9}.
Why this step? The whole point of a set is uniqueness — three 4s and two 1s land in the same buckets, so only one of each stays. So len(s) == 3.
Step 2 — try to store a list. [1, 2] is a list , which is mutable, so it is unhashable. Python cannot compute a stable address for it → TypeError: unhashable type: 'list'.
Why this step? Sets need hashable elements; a value that can change would change its own address after storage.
Step 3 — swap in a tuple. (1, 2) is a tuple — immutable, hence hashable. So ok == {(1, 2), 3} and it has 2 elements.
Why this step? Replacing the mutable list with an immutable tuple gives a value whose hash never changes, so the set can safely store it — this is the standard fix for the previous error.
Verify: {1,4,9} has 3 members ✔ (checked in VERIFY). Sanity: distinct values in [4,4,4,1,1,9] are { 1 , 4 , 9 } — exactly 3. ✔
The empty set ∅ (written set()) is the neutral element . Just like x + 0 = x and x × 0 = 0 in arithmetic, the empty set has a fixed effect on every operation.
What you are looking at (Figure s02): on the left, a solid red-filled circle labelled A = {1,2,3}; on the right, a dashed empty box labelled E = set(). A red annotation across the top reads "A | E = A (identity)" pointing at the red circle — union adds nothing, so A survives whole. A black arrow points at the dashed empty box with the label "A & E = set() (zero)" — the shared region with an empty set is always empty. Use it to see why some answers keep A intact and others collapse to nothing.
Worked example 2 — Empty set against a full set
A = { 1 , 2 , 3 }
E = set ()
print (A | E) # Forecast: ___
print (A & E) # Forecast: ___
print (A - E) # Forecast: ___
print (E - A) # Forecast: ___
print (A ^ E) # Forecast: ___
Forecast: predict all five before reading on.
Step 1 — union. "In A or E." Nothing is in E, so the OR is decided entirely by A → {1,2,3}.
Why this step? ∅ is the identity of union — like adding 0, it contributes nothing, so A comes through unchanged (the whole red circle in the figure).
Step 2 — intersection. "In A and E." Nothing is in E, so the AND can never be satisfied → set().
Why this step? ∅ is the zero of intersection — like multiplying by 0, it destroys everything, because the shared middle (the empty dashed box) has nothing in it.
Step 3 — difference A − E. "In A, not in E." Nothing is removed → {1,2,3}.
Why this step? Difference deletes exactly the elements that appear in the right operand; E holds none, so A loses nothing.
Step 4 — difference E − A. "In E, not in A." E has nothing to keep → set().
Why this step? The left operand supplies the survivors; an empty left operand can never contribute anyone, so the result is empty no matter what A holds.
Step 5 — symmetric difference. "In exactly one." Everything in A is in exactly one (only A) → {1,2,3}.
Why this step? Nothing is shared with an empty set, so no element is "in both"; every member of A qualifies as "in exactly one," giving A back.
Verify: all five checked in VERIFY. Sanity via the identity A △ ∅ = ( A ∪ ∅ ) − ( A ∩ ∅ ) = A − ∅ = A . ✔
Worked example 3 — No overlap at all
A = { 1 , 2 , 3 }
B = { 8 , 9 }
print (A & B) # Forecast: ___
print (A | B) # Forecast: ___
print (A ^ B) # Forecast: ___
Forecast: what does "no shared element" do to & and to ^?
Step 1 — intersection. No value is in both → set(). Two sets whose intersection is empty are called disjoint .
Why this step? The middle lens of the Venn diagram is empty here, so "in both" is never true for anyone.
Step 2 — union. Just glue them: {1,2,3,8,9}. With no overlap, len(A|B) == len(A)+len(B) == 5.
Why this step? When nothing is double-counted, the two sizes add exactly — there is no shared middle to subtract.
Step 3 — symmetric difference. "Exactly one" = everything, because nothing is shared → {1,2,3,8,9}. For disjoint sets A ^ B == A | B.
Why this step? Symmetric difference removes the shared middle from the union; that middle is empty here, so removing it changes nothing.
Verify: VERIFY checks A&B empty, A|B and A^B equal to {1,2,3,8,9}. Sanity: ∣ A ∣ + ∣ B ∣ = 3 + 2 = 5 = ∣ A ∪ B ∣ . ✔
What you are looking at (Figure s03): one large black outlined circle labelled A = {1,2,3,4,5}, and inside it a small red-filled circle labelled B = {2,3} — B sits entirely within A's boundary, touching no edge. A red arrow points at the small circle with the label "B - A = set() (B inside A)". That single picture explains every answer below: the overlap is all of B, and B has no part sticking outside A, so B - A vanishes. Point at the red circle whenever you need to see why.
B ⊆ A means "every element of B is also in A ." In Python: B <= A (or B.issubset(A)). Here A is the superset .
Worked example 4 — Small set nested in a big one
A = { 1 , 2 , 3 , 4 , 5 }
B = { 2 , 3 }
print (B <= A) # Forecast: ___
print (A & B) # Forecast: ___
print (A - B) # Forecast: ___
print (B - A) # Forecast: ___
print (A | B) # Forecast: ___
Forecast: if B is entirely inside A, which results equal B, which equal A?
Step 1 — is B a subset? Every element of B (2,3) is in A → True.
Why this step? We test the subset relation first because it is the fact that dictates every later answer — once we know B sits inside A, the other results follow from the picture.
Step 2 — intersection. The overlap is all of B → {2,3} = B. When B <= A, A & B == B.
Why this step? B's whole circle sits inside A's, so the lens (the shared region) is the entire small circle.
Step 3 — difference A − B. Remove B's elements from A → {1,4,5} (the outer crescent).
Why this step? Difference strips out exactly the elements B contributes; what is left is the part of A outside the inner circle.
Step 4 — difference B − A. B keeps nothing A doesn't already have → set(). When B <= A, B - A is always empty.
Why this step? B lies wholly inside A, so B has no element outside A to survive the subtraction.
Step 5 — union. A already contains B → {1,2,3,4,5} = A. When B <= A, A | B == A.
Why this step? Union adds only what is new; since every member of B is already in A, nothing new enters.
Verify: all five in VERIFY. Sanity: subset relation forces B-A == set() and A|B == A. ✔
Worked example 5 — A set against itself
A = { 7 , 8 , 9 }
B = { 9 , 8 , 7 } # same members, different write order
print (A == B) # Forecast: ___
print (A & B) # Forecast: ___
print (A - B) # Forecast: ___
print (A ^ B) # Forecast: ___
Forecast: order was scrambled — does equality survive? What do the ops give when both are identical?
Step 1 — equality. Sets are unordered, so {7,8,9} == {9,8,7} is True. Write-order never matters.
Why this step? Membership, not position, defines a set (unlike lists ) — so a scrambled write order still describes the same set.
Step 2 — intersection & union. Identical circles overlap perfectly: A & B == A and A | B == A.
Why this step? When both circles are the same, the lens is the whole circle and the union adds nothing new — both collapse to A.
Step 3 — difference. "In A not in B" but B is A → set().
Why this step? Every element of A is also in B, so nothing survives the subtraction.
Step 4 — symmetric difference. "In exactly one" but everything is in both → set().
Why this step? No element is in only one of the two identical sets, so the "exactly one" region is empty.
Verify: VERIFY checks equality True, A-B and A^B both empty. Sanity: A △ A = ( A ∪ A ) − ( A ∩ A ) = A − A = ∅ . ✔
The parent warned A − B = B − A . Here is the case that makes the sign of "who keeps what" concrete.
What you are looking at (Figure s04): two side-by-side Venn diagrams. In the left panel, titled "A - B = {1,2} (left crescent)", only A's left crescent is shaded red — B and the shared lens stay white. In the right panel, titled "B - A = {5,6} (right crescent)", only B's right crescent is shaded red. The two red regions never coincide — that mismatch is the visual meaning of "difference is directional." Glance at which crescent is red to know which set kept its members.
Worked example 6 — Left crescent vs. right crescent
A = { 1 , 2 , 3 , 4 }
B = { 3 , 4 , 5 , 6 }
print (A - B) # Forecast: ___
print (B - A) # Forecast: ___
print ((A - B) == (B - A)) # Forecast: ___
Forecast: which numbers each keeps, and whether the two results are equal.
Step 1 — A − B. Keep A's members that B does not have. A has 1,2,3,4; B removes 3,4. Left → {1,2} (the left crescent).
Why this step? The left operand A supplies the candidates, and we delete the shared 3,4, leaving A's exclusive part.
Step 2 — B − A. Now keep B's members A lacks. B has 3,4,5,6; A removes 3,4. Left → {5,6} (the right crescent).
Why this step? Swapping the operands swaps the candidate pool to B, so now B's exclusive part survives instead.
Step 3 — compare. {1,2} != {5,6} → False. The operation is directional: subtracting is "who is the survivor," and swapping roles swaps survivors.
Why this step? This is the payoff — showing the two crescents are different regions proves A - B ≠ B - A, just like 5 - 3 ≠ 3 - 5 in arithmetic.
Verify: VERIFY checks A-B == {1,2}, B-A == {5,6}, and that they differ. ✔
Worked example 7 — Prove the symmetric-difference identity on numbers
The parent claimed A △ B = ( A ∪ B ) − ( A ∩ B ) . Let's verify it holds for a concrete pair.
A = { 1 , 2 , 3 , 4 }
B = { 3 , 4 , 5 , 6 }
lhs = A ^ B
rhs = (A | B) - (A & B)
print (lhs) # Forecast: ___
print (rhs) # Forecast: ___
print (lhs == rhs)
Forecast: predict A ^ B, then predict whether the two lines match.
Step 1 — union. Everything in either → {1,2,3,4,5,6}.
Why this step? Union is our starting canvas — it holds every candidate; we will carve the shared part out of it.
Step 2 — intersection (the double-counted middle). Shared → {3,4}.
Why this step? We need to know exactly which elements are "in both," because those are the ones symmetric difference must exclude .
Step 3 — subtract the middle. {1,2,3,4,5,6} - {3,4} → {1,2,5,6} — exactly the elements in one set.
Why this step? Union counts the shared middle once; symmetric difference wants it zero times, so we subtract it out — what remains is precisely the two crescents, "in exactly one."
Step 4 — compare to A ^ B. {1,2,5,6} matches the built-in → True.
Why this step? Matching the hand-built (A|B)-(A&B) against Python's own A ^ B confirms the identity holds not just by argument but by computation.
Verify: VERIFY checks lhs == rhs == {1,2,5,6}. ✔
Worked example 8 — Did A change?
A = { 1 , 2 }
B = { 2 , 3 }
C = A.union(B) # returns new set
print (A) # Forecast: ___
print (C) # Forecast: ___
A.update(B) # mutates A in place
print (A) # Forecast: ___
Forecast: after .union, is A still {1,2}? After .update, what is A?
Step 1 — .union returns. A.union(B) computes a fresh set {1,2,3} and hands it back as C. A is untouched → A prints {1,2}.
Why this step? union is a query , not a command — it builds a new object and leaves the originals alone, which is exactly the steel-manned mistake the parent warned about.
Step 2 — capture the result. C == {1,2,3}. If you forget to assign it, the merged set is lost.
Why this step? Because union returns rather than mutates, the merged set exists only in whatever variable catches it — dropping the return value throws the work away.
Step 3 — .update mutates. A.update(B) folds B into A. Now A == {1,2,3} and there is no return value.
Why this step? update is the command counterpart — it edits A in place, which is what you want when you truly mean to grow A, and is the safe deliberate alternative to accidentally relying on union.
Verify: VERIFY checks A is {1,2} right after union, then {1,2,3} after update. ✔
Worked example 9 — Newsletter overlap
Two mailing lists. List P subscribed to Python tips , list D to Data tips . You want:
(a) people who get both emails, (b) people getting only Python tips, (c) the total distinct people mailed, (d) people getting exactly one of the two.
P = { "amy" , "bob" , "cara" , "dan" }
D = { "cara" , "dan" , "eve" , "fay" }
both = P & D # Forecast: ___
only_P = P - D # Forecast: ___
total = len (P | D) # Forecast: ___
exactly1 = P ^ D # Forecast: ___
Forecast: name each set before running.
Step 1 — "both" → intersection. English "in both lists" = AND = & → {"cara","dan"}.
Why this step? We translate the word "both" into the AND operation, because intersection is precisely the "in both" region.
Step 2 — "only Python" → difference. "In P but not D" = P - D → {"amy","bob"}. Direction matters — D - P would give the only-Data people.
Why this step? "Only P" means keep P's members and drop anyone shared with D, which is exactly what left-difference P - D does.
Step 3 — "total distinct" → size of union. Merge without double-counting cara/dan: P | D = 6 names → len == 6.
Why this step? We use union rather than len(P)+len(D) because adding 4+4=8 would count the two shared people twice.
Step 4 — "exactly one" → symmetric difference. P ^ D → {"amy","bob","eve","fay"} (4 people), the two crescents.
Why this step? "Exactly one of the two" is the literal English definition of symmetric difference, so ^ gives it directly.
Verify: VERIFY checks each answer and that len(P|D) = len(P)+len(D)-len(P&D) = 4+4-2 = 6. ✔
Worked example 10 — Order of evaluation
A = { 1 , 2 , 3 , 4 }
B = { 2 , 3 }
C = { 3 , 4 , 5 }
print (A - B | C) # Forecast: ___
print (A & B <= A) # Forecast: ___
Forecast: guess how Python groups A - B | C, then guess the second line.
Step 1 — precedence of - vs |. In Python, difference - binds tighter than union |, so A - B | C groups as (A - B) | C — the A - B runs first, then that result is unioned with C.
Why this step? Getting precedence wrong is the whole trap; because - outranks |, we must compute the difference before the union, exactly as in arithmetic where × runs before +.
Step 2 — evaluate. A - B = {1,4}. Then {1,4} | C = {1,4} | {3,4,5} = {1,3,4,5}.
Why this step? We follow the grouping we just established, doing the inner difference first and unioning its output with C.
Step 3 — the comparison line. A & B = {2,3}. Then {2,3} <= A asks "is {2,3} a subset of A?" — yes → True.
Why this step? The & operator binds tighter than the <= comparison, so the intersection is formed first and then tested for being a subset of A.
Verify: VERIFY checks both lines — A - B | C == {1,3,4,5} and (A & B) <= A is True. ✔
Recall Quick self-test across the matrix
Disjoint sets: what is A & B? ::: set() — nothing shared.
If B <= A, what is A | B? ::: A itself — B adds nothing new.
Empty set is the identity of which op and the zero of which? ::: Identity of union , zero of intersection .
Why is A - B ≠ B - A? ::: Difference is directional — it keeps the survivors of whoever is on the left .
Does A.union(B) change A? ::: No — it returns a new set; A.update(B) is the mutating version.
len(P | D) in terms of the parts? ::: len(P) + len(D) - len(P & D) (inclusion–exclusion).
What does the symbol A △ B mean? ::: Symmetric difference — the same as A ^ B, "in exactly one."
Mnemonic The two crescents and the lens
Every set answer is a coloured region : two crescents (the "only-mine" parts) plus one lens (the shared middle). Union = all three. Intersection = lens only. Difference = one crescent. Symmetric difference = both crescents, no lens.
1.2.25 Sets — unique elements, set operations (union, intersection, difference) (Hinglish) — parent topic
Boolean Logic — AND, OR, XOR — the AND/OR/XOR behind &, |, ^
Hashing and Hashable Types — why lists get rejected in C1
Tuples — immutable sequences — the hashable fix
Lists — ordered, indexed collections — order matters there, not in C5
Big-O Notation & Time Complexity — why membership is fast