Dictionaries and Sets
Topic — Two collections that aren't accessed by position. A
dictlooks things up by key; asetonly answers "is this in here?".
| Access by | Ordered | Duplicates | |
|---|---|---|---|
dict | key | insertion order | keys ❌, values ✅ |
set | membership only | ❌ | ❌ |
Dictionaries
A dict stores key: value pairs.
student = {"Name": "Vettri", "Age": 28, "Department": "CSE", "CGPA": 8.9}
print(list(student.values()))
['Vettri', 28, 'CSE', 8.9]
Reading values
d = {"a": 1, "b": 2, "c": 3}
print(d["b"]) # → 2
print(d.get("z")) # → None
print(d.get("z", 0)) # → 0
print(list(d.keys())) # → ['a', 'b', 'c']
print(list(d.values())) # → [1, 2, 3]
print(list(d.items())) # → [('a', 1), ('b', 2), ('c', 3)]
print(len(d)) # → 3
2
None
0
['a', 'b', 'c']
[1, 2, 3]
[('a', 1), ('b', 2), ('c', 3)]
3
Note .items() gives you a list of tuples — which is why for k, v in d.items():
unpacks cleanly.
[] vs .get()
A missing key with [] is an error:
d = {"a": 1}
print(d["z"])
KeyError: 'z'
print(d.get("z")) # → None no error
print(d.get("z", 0)) # → 0 your chosen default
print("z" in d) # → False just checking
| Form | Missing key |
|---|---|
d["z"] | raises KeyError |
d.get("z") | returns None |
d.get("z", 0) | returns 0 |
"z" in d | returns False |
Use [] when the key must exist — you want the error if it doesn't. Use .get() when
absence is normal and you have a sensible default.
Adding, changing, removing
Dicts are mutable. There's no separate "add" and "update" — assigning to a key does both, depending on whether it already exists.
student = {"Name": "Vettri", "Age": 28, "Department": "CSE", "CGPA": 8.9}
student["College"] = "SRM" # new key → added
print(student)
student["CGPA"] = 9.1 # existing key → updated
print(student)
del student["College"]
print(student)
popped = student.pop("Age")
print("popped", popped, student)
{'Name': 'Vettri', 'Age': 28, 'Department': 'CSE', 'CGPA': 8.9, 'College': 'SRM'}
{'Name': 'Vettri', 'Age': 28, 'Department': 'CSE', 'CGPA': 9.1, 'College': 'SRM'}
{'Name': 'Vettri', 'Age': 28, 'Department': 'CSE', 'CGPA': 9.1}
popped 28 {'Name': 'Vettri', 'Department': 'CSE', 'CGPA': 9.1}
.update() merges another dict in, overwriting on collision. .setdefault() reads a key,
inserting a default only if it's absent:
e = {"a": 1}
e.update({"b": 2, "a": 99})
print(e) # → {'a': 99, 'b': 2} 'a' overwritten
print(e.setdefault("c", 3), e) # → 3 — 'c' was missing, so inserted
print(e.setdefault("a", 100), e) # → 99 — 'a' existed, default ignored
{'a': 99, 'b': 2}
3 {'a': 99, 'b': 2, 'c': 3}
99 {'a': 99, 'b': 2, 'c': 3}
Iterating
Looping a dict directly gives keys:
d = {"name": "Vettri", "age": 28}
for k in d:
print(k)
for v in d.values():
print(v)
for k, v in d.items():
print(f"{k}: {v}")
name
age
Vettri
28
name: Vettri
age: 28
Nesting and comprehensions
nd = {"person": {"name": "Alice", "age": 30}}
print(nd["person"]["name"]) # → Alice
Alice
print({x: x * x for x in range(1, 5)})
print({k: v for k, v in {"a": 1, "b": 2, "c": 3}.items() if v > 1})
{1: 1, 2: 4, 3: 9, 4: 16}
{'b': 2, 'c': 3}
Keys must be immutable
for key in ["name", 10, (1, 2), 3.14]:
print(f" {key!r:<10} legal")
{[1, 2]: "x"}
'name' legal
10 legal
(1, 2) legal
3.14 legal
TypeError: cannot use 'list' as a dict key (unhashable type: 'list')
| Candidate | Legal key? | Why |
|---|---|---|
"name" | ✅ | str is immutable |
10 | ✅ | int is immutable |
(1, 2) | ✅ | tuple is immutable |
3.14 | ✅ | float is immutable |
[1, 2] | ❌ | list is mutable → unhashable |
A dict finds a value by computing a hash of the key and using it to pick a storage slot. If the key could change after insertion, its hash would change, and the value would be stranded in a slot nobody looks in again. Python forbids the situation instead of letting it happen.
(1, 2) is fine — but a tuple containing a list is not, for the same reason
tuple immutability is shallow.
Sets
A set holds unique, unordered values.
Duplicates vanish on creation
s = {1, 2, 2, 3, 3, 3, 4}
print(s) # → {1, 2, 3, 4}
print(len(s)) # → 4
{1, 2, 3, 4}
4
Seven values in, four out. A set stores each value by its hash, so a repeat lands in a slot that's already occupied and is simply not stored again.
print(sorted(set([3, 1, 2, 1]))) # → [1, 2, 3]
This is the standard way to deduplicate a list — though it loses the original order.
Sets of small integers often print in ascending order, which makes it look ordered. It
isn't — that's a side effect of how ints hash. Sets of strings print in a different order on
every run, because Python randomises string hashing per process. Use sorted() when order
matters.
No indexing
{1, 2, 3}[0]
TypeError: 'set' object is not subscriptable
There is no "first" element, so there's nothing for [0] to mean. Convert to a list first if
you need positions.
The empty-set trap
print(type({}).__name__) # → dict
print(type(set()).__name__) # → set
dict
set
{} is an empty dict. The only way to write an empty set is set().
Set algebra
This is what sets are really for.
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b) # → {1, 2, 3, 4, 5, 6} union — everything
print(a & b) # → {3, 4} intersection — in both
print(a - b) # → {1, 2} difference — in a only
print(a ^ b) # → {1, 2, 5, 6} symmetric difference — not in both
{1, 2, 3, 4, 5, 6}
{3, 4}
{1, 2}
{1, 2, 5, 6}
Each operator has a method form — a.union(b), a.intersection(b), a.difference(b),
a.symmetric_difference(b) — which read better when the other side isn't already a set.
Comparison operators test containment:
print({1, 2} <= {1, 2, 3}) # → True subset
print({1, 2, 3} >= {1, 2}) # → True superset
print({1, 5}.isdisjoint({2, 3})) # → True nothing in common
True
True
True
Changing a set
c = {"red", "green"}
c.add("blue")
print(sorted(c)) # → ['blue', 'green', 'red']
c.discard("nope") # not there — no error
print(sorted(c))
c.remove("nope") # KeyError: 'nope'
['blue', 'green', 'red']
['blue', 'green', 'red']
.discard() is silent on a missing value; .remove() raises KeyError. Pick whichever
matches your intent.
Membership is the point
Checking x in some_set is dramatically faster than x in some_list for large collections —
a hash lookup instead of scanning every element. If your code does a lot of "have I seen this
already?", a set is the right container.
Booleans in conditions
Comparisons produce bool, which is what if consumes:
x, y = 10, 10
print(x == y) # → True
print(x != y) # → False
True
False
Any value can act as a condition — 0, "", [], {}, None and False are falsy,
everything else is truthy. Some of those results surprise people (bool(" ") and
bool("False") are both True); the full table is in
Values and Types.
Common Mistakes
| # | Mistake | Wrong | Right |
|---|---|---|---|
| 1 | {} for an empty set | it's a dict | set() |
| 2 | Indexing a set | s[0] → TypeError | convert: list(s)[0] |
| 3 | Relying on set order | varies per run for strings | sorted(s) |
| 4 | d["missing"] when absence is normal | KeyError | d.get("missing", default) |
| 5 | List as a dict key | TypeError: unhashable | use a tuple |
| 6 | .remove() on a maybe-absent value | KeyError | .discard() |
| 7 | Expecting a set to keep duplicates | {1, 1, 2} has 2 items | use a list |
| 8 | True and 1 as separate set items | {1, True} has 1 item | True == 1, so they collide |
That last one follows from bool being a subclass of int — see
the boolean surprise.
Summary
dict
| Operation | Syntax |
|---|---|
| Create | {"k": v} or dict(k=v) |
| Read | d["k"] / d.get("k", default) |
| Add or update | d["k"] = v |
| Remove | del d["k"] / d.pop("k") |
| Merge | d.update(other) |
| Keys / values / pairs | .keys() / .values() / .items() |
| Test a key | "k" in d |
| Build | {k: v for ... in ...} |
set
| Operation | Syntax |
|---|---|
| Create | {1, 2, 3} — empty is set() |
| Add / remove | .add(x) / .discard(x) / .remove(x) |
| Union / intersection | a | b / a & b |
| Difference / symmetric | a - b / a ^ b |
| Subset / superset | a <= b / a >= b |
| Deduplicate a list | set(lst) |
Key takeaways
d["k"]raises on a missing key;.get("k", default)doesn't- Assigning to a key adds it or updates it — there's no separate operation
.items()yields tuples, which is what makesfor k, v in d.items():work- Keys must be immutable (hashable) —
str,int,float,tuple; neverlist {}is an empty dict;set()is the only way to write an empty set- Sets drop duplicates and have no order or indexing —
sorted()when you need order |,&,-,^are union, intersection, difference, symmetric differencex in some_setis far faster thanx in some_listat scale
See also: Tuples for why tuples make valid keys ·
Lists for the ordered mutable alternative ·
Data Types for dict and set in context
Run It Yourself
# --- dict ---
student = {"Name": "Vettri", "Age": 28, "Department": "CSE", "CGPA": 8.9}
print(f"{'dict':<14} {student}")
print(f"{'values':<14} {list(student.values())}")
print(f"{'get Name':<14} {student.get('Name')}")
print(f"{'get missing':<14} {student.get('College', 'not set')}")
student["College"] = "SRM"
student["CGPA"] = 9.1
print(f"{'after edits':<14} {student}")
for key, value in student.items():
print(f" {key:<12} {value}")
# --- set ---
marks = [85, 90, 75, 90, 85, 95]
unique = set(marks)
print(f"{'raw list':<14} {marks}")
print(f"{'deduplicated':<14} {sorted(unique)}")
print(f"{'count':<14} {len(marks)} -> {len(unique)}")
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(f"{'union':<14} {sorted(a | b)}")
print(f"{'intersection':<14} {sorted(a & b)}")
print(f"{'difference':<14} {sorted(a - b)}")
print(f"{'symmetric':<14} {sorted(a ^ b)}")
dict {'Name': 'Vettri', 'Age': 28, 'Department': 'CSE', 'CGPA': 8.9}
values ['Vettri', 28, 'CSE', 8.9]
get Name Vettri
get missing not set
after edits {'Name': 'Vettri', 'Age': 28, 'Department': 'CSE', 'CGPA': 9.1, 'College': 'SRM'}
Name Vettri
Age 28
Department CSE
CGPA 9.1
College SRM
raw list [85, 90, 75, 90, 85, 95]
deduplicated [75, 85, 90, 95]
count 6 -> 4
union [1, 2, 3, 4, 5, 6]
intersection [3, 4]
difference [1, 2]
symmetric [1, 2, 5, 6]
Practice Questions
From the Unit 1 question bank. Tags and marks are explained on the Python index.
Unit 1 § G — Dictionaries, Sets & Booleans
G1. [PROG] Create a dictionary holding Name, Age, Department, CGPA. Print all the
values. [3]
G2. [PROG] Using that dictionary: add a new key "College", then update the CGPA, then
display the final dictionary. [3]
G3. [OUT] [5]
d = {"a": 1, "b": 2, "c": 3}
print(d["b"])
print(d.get("z"))
print(d.get("z", 0))
print(list(d.keys()))
print(list(d.values()))
print(list(d.items()))
print(len(d))
G4. [OUT] Name the error, and say which line of G3 would have avoided it. [2]
d = {"a": 1}
print(d["z"])
G5. [OUT] [3]
s = {1, 2, 2, 3, 3, 3, 4}
print(s)
print(len(s))
Explain in one sentence why the duplicates disappeared.
G6. [OUT] [4]
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(a ^ b)
G7. [PROG] Accept two numbers from the user and print whether they are equal, using a
Boolean expression. [3]
G8. [OUT] Truthiness. Two of these are True and will surprise you. [5]
print(bool(0))
print(bool(1))
print(bool(-1))
print(bool(""))
print(bool(" "))
print(bool([]))
print(bool([0]))
print(bool("False"))
print(bool(None))
G9. [THEORY] A dictionary key must be of an immutable type. Explain why, and say which of
these are legal as keys: "name", 10, (1, 2), [1, 2], 3.14. [3]
Viva
- Why do duplicates vanish from a set?