Dictionaries and Sets
Beginner⏱55 minutesπŸ“–20 min readLesson 8 of 10

Dictionaries and Sets

Use keyed lookups, safe defaults, insertion order and set algebra to count, group and deduplicate data without silent failures

A support engineer watches a checkout page fail for one customer and work for the next. The cart totals are fine, the payment service is up, and the logs point at a single line. The code reads cart["discount"] after a coupon expires, and the missing key raises KeyError before the order can be saved.

The same program also builds a list of product tags, and the list grows with duplicates on every visit. Nobody notices until a report counts 40,000 tag views for a catalogue with 2,000 products. Two ordinary questions sit behind both failures: how do I find a value by name, and how do I keep only the distinct items?

A dictionary answers the first question, because it maps a key to a value and finds the value without scanning. A set answers the second, because it stores each distinct item once and offers fast membership tests. Both rest on the same idea: a hash turns a value into a position, and a table uses that position to skip the search.

Key terms

Dictionary
A mutable mapping of unique keys to values, written with braces such as {"tea": 3}.
Key
The hashable label used to find one value in a dictionary, such as a name or an identifier.
Value
The object stored under a key, which may be any type including a list or another dictionary.
Hashable
Able to produce a stable hash value, which makes an object usable as a dictionary key or a set member.
KeyError
The error raised by square-bracket lookup or remove when the requested key is absent.
View
A live window onto a dictionary's keys, values or items that reflects later changes instead of copying them.
Set
A mutable collection of unique hashable elements with no index and no promised order.
Frozenset
An immutable and hashable set that can be a dictionary key or an element of another set.
Union
A set operation that keeps every element found in either input set.
Intersection
A set operation that keeps only the elements shared by both inputs.
Collision
A case where two different keys land in the same table slot and must be compared one by one.
Deduplication
The removal of repeated items so that each distinct value appears once.

This lesson is the eighth stop of the course. By the end you will read and change dictionaries without a KeyError, merge them without surprising a caller, and use sets for membership, shared members and duplicate removal. You will also see why a list can never be a key, why set order cannot be trusted, and what average O(1) really promises.

1. The Key That Was Not There

A dictionary is a mutable mapping from unique keys to values, and it keeps the insertion order of those keys. The literal uses braces around comma-separated pairs, with a colon between each key and its value.

python
prices = {"tea": 3, "coffee": 4, "juice": 5} print(prices["tea"]) # 3 print(prices["juice"]) # 5 print(len(prices)) # 3

The key does the work that an index does for a list. A list finds an item by counting positions, while a dictionary finds a value by hashing its key and looking in the matching slot. That difference is why a dictionary does not need the keys to be numbers or to sit in a neat sequence.

Keys must be unique, so assigning a key that already exists replaces its value instead of adding a second pair. Values may be any object, including a list, a tuple, a function or another dictionary. Duplicate keys in one literal keep the last value, because the later pair overwrites the earlier one.

The square-bracket lookup is strict. It returns the value when the key exists and raises KeyError when the key is absent, which is a deliberate choice for code that must not continue with a wrong value.

python
>>> prices["water"] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'water'

The error is useful at the edge of a program, because a missing price should stop the order rather than become a free drink. The error is painful in the middle of a report, where one absent key should mean zero or an empty group. The next sections show how to tell those two cases apart.

100%
Description

A flowchart for the section 1. The Key That Was Not There, showing the path and decision points described in the surrounding text.

ℹ️Note

πŸ“š Did you know? Python keeps its own module cache in a dictionary named sys.modules, so every import checks a keyed table before it loads a file. The same structure that stores your prices also stores every module your program has imported.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A program runs prices = {"tea": 3, "coffee": 4}, then prints prices["juice"]. What happens?

2. Creating a Dictionary

A dictionary can be built from a literal, from an iterable of pairs, from keyword arguments, or from a comprehension. Each form produces the same mapping, and the choice is about where the data comes from.

python
empty = {} literal = {"name": "Ada", "score": 92} pairs = [("name", "Ada"), ("score", 92)] from_pairs = dict(pairs) from_names = dict(name="Ada", score=92) print(from_pairs == from_names) # True

The call dict(pairs) consumes an iterable of two-item pairs, so a list of tuples, a generator or a zip result all work. The keyword form is shorter when the keys are valid identifiers, because each name becomes a string key.

An empty dictionary is written as {}. That same brace shape also builds a set when it contains items, which is the source of a trap we meet in section 9. For now, remember that empty braces always mean an empty dictionary.

An empty dictionary is also a useful starting point for a loop, because assignment adds each key without a separate existence check.

πŸ’‘Tip

Build a dictionary from the shape of your data. Use a literal for a fixed record, dict(pairs) for rows from a file, and a comprehension when each key needs a computed value.

3. Reading Safely with get and setdefault

The method get is the quiet lookup. It returns the value when the key exists, returns None when the key is absent, and returns a supplied default when you pass one as the second argument.

python
prices = {"tea": 3, "coffee": 4} print(prices.get("tea")) # 3 print(prices.get("water")) # None print(prices.get("water", 0)) # 0 print(prices.get("water", 2.5)) # 2.5

The membership test key in d checks keys, never values. A test for a value needs value in d.values(), which scans the values and loses the fast lookup that a dictionary offers.

python
prices = {"tea": 3, "coffee": 4} print("tea" in prices) # True print(3 in prices) # False print(3 in prices.values()) # True

The method setdefault does two jobs in one step. It returns the existing value when the key is present, and it inserts the supplied default and returns it when the key is absent. That makes it the natural tool for grouping items into lists.

python
orders = {} orders.setdefault("ada", []).append("A1") orders.setdefault("ada", []).append("A2") orders.setdefault("grace", []).append("B1") print(orders) # {"ada": ["A1", "A2"], "grace": ["B1"]}

The first call inserts an empty list and returns it, so the append lands in that list. The second call finds the same list and returns it again, so the second order joins the first. A missing key never causes a separate existence check.

The common mistake is d.get(key, []).append(item). The get call builds a fresh empty list on every miss, the append changes that throwaway list, and the dictionary still has no key. The data disappears with no error, which makes the bug far harder to see than a KeyError.

⚠️Warning

get(key, []) does not store the default. Use setdefault(key, []) when the empty list must survive the call, or use a defaultdict(list) as shown in section 12.

Fill in the Blanks
How to answer

Fill-in-the-blank exercise with 2 blanks. Type the missing words, then check your answer.

Complete the safe lookup and the one-step grouping call.

The expression prices.blank("water", 0) returns zero when the key is absent. The call orders.blank("ada", []).append("A1") creates the list only when ada is missing.

Word Bank, drag words into the blanks above:

get
setdefault
pop
update
fromkeys
Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A program runs orders = {}, then orders.setdefault("ada", []).append("A1"), then orders.setdefault("ada", []).append("A2"), then prints orders["ada"]. What is printed?

4. Changing Entries: Assign, Delete, pop and popitem

Assignment is the main write operation. A new key adds a pair, and an existing key replaces its value while keeping its original position in the order.

python
stock = {"bolts": 12, "nuts": 8} stock["bolts"] = 20 # replace stock["washers"] = 30 # insert print(stock) # {"bolts": 20, "nuts": 8, "washers": 30}

The statement del d[key] removes a pair and raises KeyError when the key is absent. The method pop(key) removes and returns the value, and pop(key, default) returns the default instead of raising when the key is missing.

python
stock = {"bolts": 12, "nuts": 8} count = stock.pop("bolts") print(count) # 12 print(stock) # {"nuts": 8} print(stock.pop("gears", 0)) # 0

The method popitem() removes and returns the last inserted pair as a two-item tuple, in last-in first-out order since Python 3.7. It raises KeyError when the dictionary is empty, so a drain loop needs a guard or a try block.

python
recent = {"first": 1, "second": 2, "third": 3} print(recent.popitem()) # ("third", 3) print(recent) # {"first": 1, "second": 2}

The state diagram follows one key through the operations. A replacement keeps the key in place, while a delete followed by a fresh insert sends it to the end of the order.

100%
Description

A state diagram for the section 4. Changing Entries: Assign, Delete, pop and popitem, showing the states and transitions described in the surrounding text.

πŸ“ŒImportant

A deletion is not a reset. Once a key is removed and added again, the dictionary treats it as a new insertion, so any code that depends on the old order needs a different design.

Matching Question
How to answer

Matching exercise with 5 pairs. Match each item on the left with its answer on the right.

Match each dictionary operation to the result it produces.

d[key] = value
Drop here
d.pop(key)
Drop here
d.pop(key, 0)
Drop here
d.popitem()
Drop here
del d[key]
Drop here

Drag answers from here:

inserts a pair or replaces the value in place
removes the pair and returns its value
removes the pair or returns 0 when absent
removes and returns the last inserted pair
removes the pair and returns nothing

5. Merging with update, the Pipe and the Pipe-Assign

Three tools merge dictionaries, and they differ in who owns the result. The method update(other) changes the dictionary in place, d1 | d2 builds a new dictionary, and d1 |= d2 changes d1 in place.

python
defaults = {"theme": "light", "font": 12} user = {"font": 14, "lang": "en"} merged = defaults | user print(merged) # {"theme": "light", "font": 14, "lang": "en"} print(defaults) # {"theme": "light", "font": 12}

The right operand wins on a duplicate key, so the user font replaces the default font. The original defaults dictionary keeps its own value, because the pipe builds a new mapping and leaves both inputs untouched.

The in-place form is a different contract. The statement defaults |= user updates defaults and can surprise a caller that still holds a reference to the old mapping.

python
defaults = {"theme": "light", "font": 12} user = {"font": 14} defaults |= user print(defaults) # {"theme": "light", "font": 14}

The union operators arrived in Python 3.9 through PEP 584, so code that must run on older interpreters needs update or a dictionary unpacking expression. Every supported release in November 2026 includes them, which makes the pipe safe for new code.

update also accepts an iterable of pairs, so it merges rows from a file as easily as another dictionary.

Call
Changes the left dict?
Returns
d1.update(d2)
yes
None
d1 | d2
no
a new dict
d1 |= d2
yes
None
{**d1, **d2}
no
a new dict
πŸ’‘Tip

Use the pipe when a function should return a merged copy, and use the pipe-assign when the caller explicitly wants an in-place update. The name of the operator tells the next reader which contract to expect.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A program runs left = {"a": 1}, then right = {"a": 2, "b": 3}, then merged = left | right. What do left and merged hold afterward?

6. Keys, Values, Items and Insertion Order

The methods keys(), values() and items() return view objects. A view is a live window onto the dictionary, so it reflects later changes and does not copy the data.

python
scores = {"ada": 92, "grace": 95} keys = scores.keys() print(list(keys)) # ["ada", "grace"] scores["linus"] = 88 print(list(keys)) # ["ada", "grace", "linus"]

The second print includes the new key, because the view points at the dictionary rather than at a snapshot. Building a list from the view freezes the current members, which is useful when the loop must not see later changes.

Insertion order is a language guarantee from Python 3.7 onward. Iterating a dictionary walks keys in the order they were first inserted, and replacing a value does not move its key. Deleting a key and adding it again does move it to the end.

python
steps = {"mix": 1, "bake": 2, "cool": 3} steps["bake"] = 20 print(list(steps)) # ["mix", "bake", "cool"] del steps["mix"] steps["mix"] = 10 print(list(steps)) # ["bake", "cool", "mix"]

A loop over the dictionary reads the live mapping, so adding or deleting keys during that loop raises RuntimeError. The safe pattern collects the keys first, then applies the changes in a second pass.

python
counts = {"a": 1, "b": 0, "c": 2} for key in list(counts): if counts[key] == 0: del counts[key] print(counts) # {"a": 1, "c": 2}

The list(counts) call copies the keys, so the loop walks a fixed list while the dictionary changes. Without the copy, the interpreter detects the size change and stops the program.

🚨Danger

Never add or delete keys inside a loop that walks the dictionary directly. The program raises RuntimeError: dictionary changed size during iteration, and the failure appears only when the branch actually runs.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A program runs d = {"a": 1, "b": 2}, then del d["a"], then d["a"] = 3, then prints list(d). Which list is printed?

7. Hashable Keys and the Unhashable Error

A key must be hashable, which means it can produce a stable hash value for the life of the lookup. Strings, numbers, None, tuples of hashable items and frozensets qualify. Lists, dictionaries and sets do not, because they can change after they are stored.

python
valid = {} valid["name"] = "Ada" valid[42] = "answer" valid[(3, 4)] = "point" valid[frozenset({1, 2})] = "group" print(len(valid)) # 4

The tuple works because both of its items are integers, and a frozenset works because it is immutable. A tuple is hashable only when every item inside it is hashable, so (3, [4]) fails even though the outer object is a tuple.

python
>>> d = {} >>> d[[3, 4]] = "point" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list'

The error protects the table, because a key that could change would leave its entry in the wrong slot. Python therefore refuses mutable keys at the door rather than corrupting the lookup later.

A tuple is the usual key for a compound value such as a coordinate, because its positions carry meaning and its contents can be checked once. A frozenset is the usual key when the compound value is a group whose order does not matter.

ℹ️Note

πŸ“š Did you know? Equal frozensets are interchangeable as keys, so frozenset({1, 2}) and frozenset({2, 1}) name the same entry. The order of the original set never enters the key.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A program needs one dictionary key that represents a point with an x value and a y value. Which value can be used as that key?

8. Dict Comprehensions and the fromkeys Trap

A dictionary comprehension builds a mapping from an iterable in one expression. The braces hold a key expression, a colon, a value expression and a loop header, which reads like a compact record builder.

python
words = ["tea", "coffee", "juice"] lengths = {word: len(word) for word in words} print(lengths) # {"tea": 3, "coffee": 6, "juice": 5}

The expression runs once per item, so every key receives its own computed value. A condition can filter the source, and a second loop can walk a nested structure when the data needs pairs.

The class method fromkeys(iterable, value) looks like a shortcut, and it is safe when the value is immutable. It builds one mapping with a key for every item and the same value for each, which is exactly what a set of flags needs.

python
flags = dict.fromkeys(["read", "write", "admin"], False) flags["write"] = True print(flags) # {"read": False, "write": True, "admin": False}

The trap appears when the shared value is mutable. With a list default, every key points at one list object, so an append through one key appears under every key.

python
groups = dict.fromkeys(["ada", "grace"], []) groups["ada"].append("A1") print(groups) # {"ada": ["A1"], "grace": ["A1"]}

The output looks impossible until you remember that fromkeys stores the same list once and copies only the reference. A dictionary comprehension runs the value expression per key, so each key receives a fresh list.

python
groups = {name: [] for name in ["ada", "grace"]} groups["ada"].append("A1") print(groups) # {"ada": ["A1"], "grace": []}

The comprehension is the correct default for new containers, and fromkeys remains the right tool for a shared immutable value such as False, 0 or None.

⚠️Warning

dict.fromkeys(keys, []) gives every key the same list. If one key appends to it, every key shows the change, and the bug is silent because no error is raised.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A program runs d = dict.fromkeys(["a", "b"], []), then d["a"].append(1), then prints d. What does it print?

9. Sets: Unique, Unordered Collections

A set stores unique hashable elements, and it has no index, no slice and no promised order. A literal uses braces with items, while set() builds an empty set or converts any iterable.

python
tags = {"news", "sport", "news", "weather"} print(tags) # {"news", "sport", "weather"} in some order print(len(tags)) # 3 numbers = set([1, 2, 2, 3, 3, 3]) print(numbers) # {1, 2, 3} empty = set() print(type(empty)) # <class 'set'>

The duplicate strings collapse because a set keeps one copy of each equal value. The empty braces {} build an empty dictionary, so set() is the only clear way to write an empty set.

The method add(x) inserts one element and does nothing when the element is already present. The method remove(x) deletes an element and raises KeyError when it is absent, while discard(x) deletes it and stays silent. The method pop() removes and returns an arbitrary element and raises KeyError when the set is empty, and clear() empties the set.

python
seen = set() seen.add("ada") seen.add("ada") print(len(seen)) # 1 seen.discard("grace") # no error removed = seen.pop() print(removed) # "ada", the only element

Set iteration order is not guaranteed, because it depends on hashes and on insertion history. String hashes are randomized per process unless PYTHONHASHSEED is fixed, so two runs can print the same set in different orders.

ℹ️Note

πŸ“š Did you know? The pop method on a set does not mean first or last. It removes an arbitrary element, so a program that needs a specific member should call remove or discard instead.

Practice Question
How to answer

Multiple-choice question with 2 options. Select one option, then choose Check Answer.

A program runs box = {}. Which statement correctly describes the value of box?

Practice Question
How to answer

Multiple-choice question with 2 options. Select one option, then choose Check Answer.

A set named tags holds {"news", "sport"}. The call tags.remove("weather") raises KeyError, while tags.discard("weather") returns without an error.

10. Set Algebra, Subsets and frozenset

Sets support four core operations. Union keeps every member of either input, intersection keeps only the shared members, difference keeps the members of the left set that are not in the right, and symmetric difference keeps the members found in exactly one input.

python
a = {1, 2, 3, 4} b = {3, 4, 5} print(a | b) # {1, 2, 3, 4, 5} print(a & b) # {3, 4} print(a - b) # {1, 2} print(a ^ b) # {1, 2, 5}

The operators require both operands to be sets, while the methods union, intersection, difference and symmetric_difference accept any iterable. That difference matters when the right side is a list from a file and you do not want to convert it first.

python
a = {1, 2, 3} print(a.union([3, 4])) # {1, 2, 3, 4} print(a.intersection([2, 3])) # {2, 3}

Subset and superset tests answer questions about containment. The operators <= and < test subset, with < excluding equality, and the methods issubset and issuperset say the same thing in words. The method isdisjoint reports whether two sets share no members at all.

python
small = {1, 2} large = {1, 2, 3} print(small <= large) # True print(small < large) # True print(large >= small) # True print(small.isdisjoint({4, 5})) # True

The diagram shows the four operations on two overlapping sets. Each arrow carries the rule that decides which members survive.

100%
Description

A flowchart for the section 10. Set Algebra, Subsets and frozenset, showing the path and decision points described in the surrounding text.

In-place set operators include |=, &=, -= and ^=, plus the update methods update, intersection_update, difference_update and symmetric_difference_update. The in-place forms change the left set and return None, which matches the pattern you saw with lists.

A frozenset is an immutable and hashable set. It supports the same algebra, but it cannot change, so it can serve as a dictionary key or as an element of another set.

python
group = frozenset({1, 2}) lookup = {group: "first pair"} print(lookup[frozenset({2, 1})]) # first pair

The second frozenset equals the first, so the lookup finds the same entry. A mutable set could not play that role, because a change after insertion would damage the table.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A program runs a = {1, 2, 3, 4} and b = {3, 4, 5}, then prints `a - b`. Which set is printed?

11. Deduplication Without Losing Order

Two idioms remove duplicates, and only one of them keeps the original order. The expression list(set(items)) removes repeats but may reorder the result, because set iteration order is not guaranteed. The expression list(dict.fromkeys(items)) removes repeats and keeps first-seen order on Python 3.7 and later.

python
items = [3, 1, 3, 2, 1] print(list(set(items))) # order varies print(list(dict.fromkeys(items))) # [3, 1, 2]

The dictionary version works because a key can appear only once, and the insertion order of the first appearance survives. The value is None for every key, which is irrelevant because only the keys are read back.

πŸ’‘Tip

Use dict.fromkeys(items) when order matters and set(items) when it does not. The second form is shorter, and the first form is the one that produces a stable list.

12. Counter and defaultdict

The collections module ships two shortcuts that remove boilerplate from counting and grouping. A Counter counts hashable items and returns a dictionary-like mapping, while a defaultdict creates a missing value by calling a factory function.

python
from collections import Counter text = "the quick brown fox jumps over the lazy dog" counts = Counter(text.split()) print(counts["the"]) # 2 print(counts.most_common(1)) # [("the", 2)] print(len(counts)) # 8

The sentence has nine words, and only "the" repeats, so the counter holds eight keys and the counts sum to nine. The method most_common(n) returns the n most frequent pairs, which makes a leaderboard a one-line operation.

A defaultdict solves the grouping problem without a setdefault call on every line. The factory runs only when a key is missing, so defaultdict(int) starts every count at 0 and defaultdict(list) starts every group as an empty list.

python
from collections import defaultdict orders = defaultdict(list) for name, item in [("ada", "A1"), ("grace", "B1"), ("ada", "A2")]: orders[name].append(item) print(dict(orders)) # {"ada": ["A1", "A2"], "grace": ["B1"]}

The first append to a new name triggers the factory, which builds a fresh list for that key. A plain dictionary would need an existence check or a setdefault call, and the factory keeps the intent visible at the top of the block.

ℹ️Note

πŸ“š Did you know? A Counter returns 0 for a missing key instead of raising KeyError, because it is designed for counting. That behaviour is convenient in a tally and dangerous when a zero would hide a real data problem.

13. Average O(1) and the Collision Caveat

Membership in a dictionary or a set is O(1) on average, while a membership test on a list is O(n). The difference is the search: a list checks items one by one, and a hash table jumps to the likely slot in one step.

python
items = list(range(100_000)) lookup = set(items) print(99999 in items) # scans the list print(99999 in lookup) # hashes the value

The list scan grows with the list, while the set lookup stays roughly flat as the set grows. The guarantee is average case, not worst case. A collision happens when two different keys land in the same slot, and the table must compare the stored keys one by one until it finds a match or proves absence. A pathological set of keys can therefore degrade toward O(n), even though ordinary data almost never does.

The sequence diagram follows one lookup through the hash, the bucket and the entry chain. The note marks the collision path, where the table checks more than one entry before it answers.

100%
Description

A sequence diagram for the section 13. Average O(1) and the Collision Caveat, showing the order of messages and replies described in the surrounding text.

A list scan is still the right choice for a short collection, because it preserves order and needs no hashing.

πŸ“ŒImportant

O(1) describes the average, not a promise for every input. Say average O(1) when you describe dictionary or set lookup, and mention collisions when the distinction matters.

14. Choosing a Container

The choice follows the question the program asks. A dictionary answers "what value belongs to this key", a set answers "is this member present" and "which members are shared", and a list or tuple answers questions about order and position.

Need
Container
Reason
Find a record by name or id
dict
keyed lookup with average O(1)
Count occurrences
dict or Counter
each key holds a count
Group items by owner
dict or defaultdict
each key holds a list
Test membership many times
set
unique members with average O(1)
Remove duplicates and keep order
dict.fromkeys
keys keep first-seen order
Keep a fixed record
tuple
immutable positions with meaning
Keep an ordered, changing sequence
list
indexing and in-place methods
Use a group as a key
frozenset
immutable and hashable

The decision diagram turns the table into four questions. Each answer leads to the container that matches the job, and the colours separate the four families.

100%
Description

A flowchart for the section 14. Choosing a Container, showing the path and decision points described in the surrounding text.

A dictionary and a set both rely on hashing, so both need hashable members and both give up positional access. A list and a tuple both keep order, so both allow duplicate values and both answer to an index.

15. Dictionaries and Sets in the Python 3.15 World

2026 Updates (as of November 2026)

ℹ️Note

What changed recently, and why older tutorials may look different. Every line below was checked against a primary source in November 2026:

  • Python 3.15.2 is the current stable release (3.15.0 shipped on 2026-10-09, and 3.15.2 followed on 2026-10-25). The dictionary and set rules in this lesson are unchanged in 3.15, so every example runs as written.
  • Python 3.10 reached end of life on 2026-10-01. The union operators from PEP 584 arrived in 3.9, so they are available on every supported series today.

The insertion order guarantee and the pipe operators are settled language rules, so new code can rely on both without a version check.

Real-World Case Studies

JSON and web APIs. RFC 8259 defines a JSON object as an unordered collection of name and value pairs, and the Python json module maps that object to a dictionary. When an API response arrives, the fields become keys and the values keep their JSON types, so the dictionary you read is the shape of the message.

The import system. Python stores every loaded module in the sys.modules dictionary, keyed by the module name. The next import of the same name finds the cached entry in one lookup instead of reading and running the file again, which is why a long-running process imports quickly after the first call.

Text analytics. A word count over a paragraph is the smallest useful dictionary program. The sentence "the quick brown fox jumps over the lazy dog" yields eight keys and nine total counts, and a Counter produces the same answer with most_common ready for a report. The same pattern scales from a sentence to a log file without changing its shape.

ℹ️Note

πŸ“š Did you know? The JSON specification calls its objects unordered, while Python dictionaries keep insertion order. The Python mapping is therefore a convenient and deterministic home for a JSON object, even though the format itself promises no order.

Practice Questions

Answer these before the next lesson; the final two questions reach back to earlier lessons.

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A program runs names = ["ada", "grace"], then index = {}, then for position, name in enumerate(names, start=1): index[name] = position, then prints index. What is printed?

Practice Question
How to answer

Multiple-choice question with 4 options. Select one option, then choose Check Answer.

A program runs a = {"score": 1}, then b = a, then b["score"] = 2, then c = a.copy(), then c["score"] = 3. What does the program print for a["score"] and c["score"]?

What Comes Next

You can now look up a value by key, supply a default without hiding a real error, and keep a collection free of duplicates. The next lesson turns those small operations into reusable blocks, because a function is where a lookup rule becomes a named part of a program.

Before moving on, the video below is a full beginner course that replays this material in a second voice. Its chapters on dictionaries, sets and counting are a good parallel reference when you want the same ideas from another teacher.

A long-form beginner course with dedicated sections on dictionaries, sets and common collection patternsWatch on YouTube
Description

A video walkthrough that supports the section What Comes Next and repeats its main ideas in a second voice.

βœ…Success

Key Takeaways:

  1. A dictionary maps unique hashable keys to values and keeps insertion order from Python 3.7 onward, so iteration follows first insertion.
  2. A square-bracket lookup raises KeyError on a miss, while get(key, default) returns a fallback without storing it.
  3. setdefault inserts a default only when the key is absent and returns the stored value, which makes it the one-step grouping tool.
  4. update, | and |= merge dictionaries; the pipe returns a new dictionary, and the pipe-assign changes the left operand in place.
  5. Keys must be hashable, so strings, numbers, tuples of hashable items and frozensets work, while lists, dictionaries and sets raise TypeError.
  6. A dictionary comprehension gives each key its own value, while fromkeys(keys, []) gives every key the same list and creates a silent shared-state bug.
  7. A set stores unique hashable elements with no index and no promised order, and set() builds an empty set while {} builds an empty dictionary.
  8. Set algebra uses |, &, - and ^, subset tests use <= and <, and a frozenset is the immutable, hashable form of a set.
  9. list(set(items)) removes duplicates without order, while list(dict.fromkeys(items)) keeps first-seen order on modern Python.
  10. Dictionary and set lookup is average O(1) because of hashing, but collisions can degrade it, and a list scan is O(n).

References

Every factual claim links to its source, with the access date printed so the source can be rechecked.

  1. [1]Built-in typesDocumentation

    Python Software Foundation Β· Accessed 2026-11-05

  2. [2]Data StructuresDocumentation

    Python Software Foundation Β· Accessed 2026-11-05

  3. Python Software Foundation Β· Accessed 2026-11-05

  4. Python Software Foundation Β· Accessed 2026-11-05

  5. Python Software Foundation Β· Accessed 2026-11-05

  6. Python Software Foundation Β· Accessed 2026-11-05

  7. IETF Β· Accessed 2026-11-05

  8. Python Software Foundation Β· Accessed 2026-11-05

Progress80%