Skip to main content

Dictionaries and sets

A dictionary finds a value by its key, drawn as a two-column table. Look up, add and overwrite by key, test with in and get, count the five bands of the study table from scratch and then with Counter, and meet sets: no duplicates, a quick in, union, intersection and difference.
P01.6.3L0 Foundation25 minIn review

Before this lesson

P01.6.2 Tuples, unpacking and looping in pairs P01.5 Modules, packages and environments

After this lesson you can

  • Build a dictionary, look a value up by its key, add or overwrite an entry, and explain the KeyError.
  • Test for a key with in, and read a value safely with get and a default.
  • Count how often each value occurs with a dictionary written from scratch, then with collections.Counter.
  • Remove duplicates with a set, and find the union, intersection and difference of two sets.

In review. The maths and code on this page are checked by automated tests on every change, but an independent reader has not signed it off yet. Spot a mistake or a confusing step? Tell us.

The question

With cut-offs 70 and 50 the five scores fall into the bands pass, pass, distinction, fail, pass. How many of each? And how would you find Chen’s score by her name, without remembering that she sits at position 2? A list answers “what is at position 2?”. This lesson’s containers answer “what goes with the word 'Chen'?”, the same question a language model asks when it turns every word into a number.

From the last lesson

1. What does name, hours, score = ('Ama', 2, 55) do?

Show answer

It unpacks the tuple: name is bound to 'Ama', hours to 2 and score to 55.

2. What does zip(hours, scores) do if scores has one item fewer than hours?

Show answer

It stops when the shorter list runs out, so the last hour gets no partner and is dropped, without any error.

Intuition

A dictionary is a collection of pairs, each a key and its value. You give it the key and it gives you the value, the way a real dictionary gives you the meaning of a word. The course draws it as a two-column table: keys on the left, values on the right.

A memory diagram. The orange name score_of points to a box labelled dict, holding a two-column table with the header key and value. Its rows are 'Ama' 55, 'Ben' 65, 'Chen' 80, 'Dina' 40 and 'Eli' 60, all in blue. The row 'Chen' 80 is highlighted yellow, and a vermillion arrow from the label score_of['Chen'] points at it, with the words gives 80. Lower down, the label score_of['Femi'] in reddish purple has no row to point to, with the words KeyError: no such key.
P01.6.3-F1

A dictionary as a two-column table. score_of['Chen'] finds the row whose key is 'Chen' and gives its value, 80. There is no row for 'Femi', so that lookup fails.

Compare it with the list of lesson P01.6.1. A list finds a value by its position, a whole number from 0 that you have to know. A dictionary finds it by a key you choose, here the student’s name. The keys play the part of the positions, so each key can appear only once, just as a list has only one position 2.

A set is a dictionary with the values taken away: a collection of keys and nothing else. It holds each item at most once and answers one question quickly: is this item in me?

Build it up

Step 1. Writing a dictionary. Braces hold key: value pairs, separated by commas. Square brackets look a key up.

score_of = {'Ama': 55, 'Ben': 65, 'Chen': 80, 'Dina': 40, 'Eli': 60}
print(score_of['Chen'], len(score_of))
80 5

Step 2. Adding and overwriting. Assigning to a key that is not there yet adds a pair. Assigning to a key that is there replaces its value. Adding pair by pair builds the dictionary above from the two lists of the study table, with zip from the last lesson; then one value is overwritten:

names = ['Ama', 'Ben', 'Chen', 'Dina', 'Eli']
scores = [55, 65, 80, 40, 60]

built = {}
for name, score in zip(names, scores):
    built[name] = score
print(built == score_of, list(built))
built['Dina'] = 45
print(built['Dina'], len(built))
True ['Ama', 'Ben', 'Chen', 'Dina', 'Eli']
45 5

list(built) gives the keys in the order they were added: a dictionary remembers its insertion order (guaranteed since Python 3.7). Overwriting Dina’s score kept five keys and left her in her place.

Step 3. A key that is not there. Looking up a missing key with square brackets stops the program:

print(score_of['Femi'])
KeyError: 'Femi'

That is the last line of the traceback, copied from Python 3.15 (wording varies a little between versions). Two tools avoid it. in asks whether a key is there: 'Chen' in score_of is True and 'Femi' in score_of is False. The method get returns the value if the key is there and a default otherwise: score_of.get('Femi') is None, and score_of.get('Femi', 0) is 0.

Step 4. Looping. A for loop over a dictionary visits its keys. To get each key with its value, loop over items(), which hands over (key, value) pairs ready to unpack:

for name, score in score_of.items():
    if score >= 70:
        print(name, score)
Chen 80

Step 5. Which things can be keys. A key must never change, or the dictionary could not find it again, so keys are strings, numbers or tuples of those. A list cannot be a key: {['Ama', 2]: 55} stops with a TypeError that calls the list unhashable. The word comes from the way a dictionary finds a key: it turns the key into a number called its hash, which must stay the same for as long as the key is in the dictionary. Values, on the other hand, can be anything.

Step 6. A set. A set is written with braces but no colons, or made from a list with set:

bands = ['pass', 'pass', 'distinction', 'fail', 'pass']
kinds = set(bands)
print(len(kinds), sorted(kinds), 'fail' in kinds)
3 ['distinction', 'fail', 'pass'] True

The five bands hold only three different words, and the set keeps each once. A set keeps no order, so sorted is used to print it the same way on every run. Two sets combine like the Venn diagrams of M01.6, if you have met them: | gives the union (in either), & the intersection (in both) and - the difference (in the first but not the second).

A Venn diagram of two sets of words. The blue circle first holds 'cat' and 'mat' on its own and shares 'on', 'sat' and 'the' with the sky-blue circle second, which also holds 'dog' and 'log' on its own. Beside it, in code: first & second gives {'on', 'sat', 'the'}; first - second gives {'cat', 'mat'}; second - first gives {'dog', 'log'}; first | second gives all 7 words.
P01.6.3-F2

The words of two sentences, “the cat sat on the mat” and “the dog sat on the log”, as two sets. Each operation reads off regions of the picture. (Sets are drawn in alphabetical order; Python may print them in any order.)

The formal statement

NoteDefinition: dictionary and set

A dictionary is a changeable collection of key–value pairs in which each key appears at most once. Keys must be hashable: an object whose hash never changes, such as a string, a number or a tuple of those. A dictionary remembers the order in which keys were added.

Code Meaning
d[k] the value of key k; KeyError if k is missing
d[k] = v add the pair, or replace the value of k
k in d True if k is a key (values are not searched)
d.get(k, x) the value of k, or x if k is missing (None if x is left out)
d.items() the (key, value) pairs, in insertion order

A set is a changeable collection of distinct hashable objects, with no order and no positions. set() is the empty set; {} is an empty dictionary. For sets A and B, A | B, A & B and A - B are the union, intersection and difference.

How big is a union? Split the picture into its three regions and let \(a\), \(c\) and \(b\) be the number of items only in \(A\), in both, and only in \(B\). Then \({\abs{A} = a + c}\) and \({\abs{B} = b + c}\), where \({\abs{A}}\) is the size of \(A\), len(A) in code. Adding the two sizes counts the middle region twice:

\[ \begin{aligned} \abs{A} + \abs{B} &= (a + c) + (b + c) \\ &= (a + b + c) + c \\ &= \abs{A \cup B} + \abs{A \cap B}. \end{aligned} \]

Taking \({\abs{A \cap B}}\) from both sides gives the rule

\[ \abs{A \cup B} = \abs{A} + \abs{B} - \abs{A \cap B}. \]

For the two sentences: \({5 + 5 - 3 = 7}\) different words, which is what len(first | second) gives.

Go deeper: why in is quick for a set and a dictionary

A set or a dictionary does not search its items one by one. It turns the item into its hash, a whole number, and uses that number to work out where in its storage the item must be, then looks only there. Python’s documentation therefore lists x in s for a set, and k in d for a dictionary, as taking on average about the same time however many items there are, while x in a for a list takes time in proportion to its length. The price is the rule about keys: if a key could change, its hash would change, and the dictionary would look for it in the wrong place.

Worked example

Counting the bands from scratch. The loop keeps a dictionary counts whose keys are the bands seen so far and whose values are how often each has appeared.

counts = {}
for band in bands:
    if band in counts:
        counts[band] = counts[band] + 1
    else:
        counts[band] = 1
Six step frames in two rows. Each of the first five shows one pass of the loop: the band of that pass, a green note saying whether it is a new key or already in counts, and the dictionary counts after the pass as a small two-column table, with the changed row highlighted yellow. Pass 1, 'pass', new key: 'pass' 1. Pass 2, 'pass', add 1: 'pass' 2. Pass 3, 'distinction', new key: 'pass' 2, 'distinction' 1. Pass 4, 'fail', new key: 'pass' 2, 'distinction' 1, 'fail' 1. Pass 5, 'pass', add 1: 'pass' 3, 'distinction' 1, 'fail' 1. The sixth frame, after the loop, lists counts['pass'] gives 3, len(counts) gives 3, and the sum of the counts is 5.
P01.6.3-F3

The counting pattern, one frame per pass. A band seen for the first time becomes a new key with count 1; a band seen before has its count raised by 1. The yellow row is the one that pass changed.

Pass 1. band is 'pass'. The dictionary is empty, so the test band in counts is False and the else branch adds the key: {'pass': 1}.

Pass 2. 'pass' again. Now the test is True, and the line counts[band] = counts[band] + 1 reads 1, adds 1 and stores 2 under the same key.

Passes 3 and 4. 'distinction' and then 'fail' are new, so each is added with count 1. After pass 4 the dictionary has three keys.

Pass 5. 'pass' is there, so its count goes from 2 to 3.

The result is {'pass': 3, 'distinction': 1, 'fail': 1}: three passes, one distinction, one fail. Two checks by hand: there are 3 keys, one per different band, and the counts add up to 5, one per score. The keys appear in the order in which each band was first met.

Code

The worked example, with the bands computed from the scores by the cut-offs of P01.3.1:

def band_of(score):
    if score >= 70:
        return 'distinction'
    elif score >= 50:
        return 'pass'
    else:
        return 'fail'

bands = []
for score in scores:
    bands.append(band_of(score))

counts = {}
for band in bands:
    if band in counts:
        counts[band] = counts[band] + 1
    else:
        counts[band] = 1
print(bands)
print(counts)
['pass', 'pass', 'distinction', 'fail', 'pass']
{'pass': 3, 'distinction': 1, 'fail': 1}

The standard library has this pattern built in. Counter, from the module collections, is a dictionary made for counting: give it a list and it counts the items, and a missing key counts as 0 instead of raising KeyError.

from collections import Counter

tally = Counter(bands)
print(tally)
print(tally == counts, tally['merit'], tally.most_common(1))
Counter({'pass': 3, 'distinction': 1, 'fail': 1})
True 0 [('pass', 3)]

A vocabulary is a dictionary of the same kind: it gives every different word a number, in the order the words are first met. Text models start this way, because a model computes with numbers, not words.

words = ['the', 'cat', 'sat', 'on', 'the', 'mat']
vocab = {}
for word in words:
    if word not in vocab:
        vocab[word] = len(vocab)

ids = []
for word in words:
    ids.append(vocab[word])
print(vocab)
print(ids)
{'the': 0, 'cat': 1, 'sat': 2, 'on': 3, 'mat': 4}
[0, 1, 2, 3, 0, 4]

The sentence of six words becomes the six numbers [0, 1, 2, 3, 0, 4]; the two copies of 'the' get the same number. Finally the sets of the figure in Build it up:

first = set(['the', 'cat', 'sat', 'on', 'the', 'mat'])
second = set(['the', 'dog', 'sat', 'on', 'the', 'log'])
print(sorted(first & second), sorted(first - second), sorted(second - first))
print(len(first), len(second), len(first | second))
['on', 'sat', 'the'] ['cat', 'mat'] ['dog', 'log']
5 5 7

Explore

This module’s interactive, the box-diagram workbench, lives in lesson P01.6.4. It draws a list, but everything it shows about two names that share one object holds for a dictionary or a set just as well.

Pitfalls

Two panels side by side. Left, marked with a cross and headed add 1 to a missing key: the code counts = {}, for band in bands, counts[band] += 1, then an empty dict box labelled no keys yet, and in reddish purple the note pass 1 reads counts['pass'], which is not there, KeyError: 'pass'. Right, marked with a tick and headed start a new key at 0: the code counts = {}, for band in bands, n = counts.get(band, 0), counts[band] = n + 1, then the finished table 'pass' 3, 'distinction' 1, 'fail' 1, and in green the note get(band, 0) gives 0 for a new key.
P01.6.3-F4

Wrong beside right. Left: += 1 must first read the old count, and a new band has none. Right: get with a default of 0 supplies the missing count.

“counts[band] += 1 counts the bands.”

Only once each band has a count. counts[band] += 1 means counts[band] = counts[band] + 1, which reads the old value first, and on the first pass there is none: KeyError: 'pass'. Test with in, as in the worked example, or write counts[band] = counts.get(band, 0) + 1, or use Counter.

“80 in score_of asks whether someone scored 80.”

in looks only at the keys, so 80 in score_of is False even though Chen scored 80. Ask the values directly: 80 in score_of.values() is True.

“{} makes an empty set.”

It makes an empty dictionary. The empty set is written set().

“A set keeps the items in the order I gave them.”

A set has no order and no positions, so kinds[0] is a TypeError and printing a set of words can show a different order from one run to the next. When order matters, print sorted(kinds), or use a list or a dictionary.

Check yourself

Recall

1. What does score_of['Chen'] give, and what does score_of['Femi'] do?

Show answer

It gives 80. score_of['Femi'] stops the program with KeyError: 'Femi', because there is no such key.

2. What does score_of.get('Femi', 0) give?

Show answer

0, the default, because 'Femi' is not a key. Without the default, get gives None.

3. Why is 80 in score_of False?

Show answer

in on a dictionary checks the keys, and 80 is a value. 80 in score_of.values() is True.

4. How many items does set([55, 60, 55, 60]) hold?

Show answer

Two, 55 and 60. A set keeps each item once.

5. For first = {'the', 'cat', 'sat', 'on', 'mat'} and second = {'the', 'dog', 'sat', 'on', 'log'}, what is first & second?

Show answer

The words in both: 'on', 'sat' and 'the'.

Exercises

1. ★ Build hours_of, a dictionary from each name to that student’s hours, with zip. What are hours_of['Chen'] and hours_of.get('Femi', 0), and how long is the dictionary after hours_of['Femi'] = 3?

Show solution
hours_of = {}
for name, h in zip(names, [2, 4, 5, 1, 3]):
    hours_of[name] = h

hours_of['Chen'] is 5 and hours_of.get('Femi', 0) is 0. The assignment adds a sixth key, so len(hours_of) becomes 6.

2. ★★ The errors of predict on the study table are [4, -4, 2, -2, 0]. Count how many errors there are of each size, ignoring the sign, with a dictionary and get. Then check your answer with Counter.

Show solution

The sizes are the absolute values, 4, 4, 2, 2 and 0, and the keys can be numbers:

sizes = {}
for e in [4, -4, 2, -2, 0]:
    sizes[abs(e)] = sizes.get(abs(e), 0) + 1

sizes is {4: 2, 2: 2, 0: 1}: two errors of size 4, two of size 2 and one of 0. Building Counter from the list of sizes, [4, 4, 2, 2, 0], gives the same counts, and the counts add up to 5, one per student.

3. ★★★ Using only |, & and -, find the words that are in exactly one of the two sentences first and second. Predict how many there are before you compute them, and show that (first - second) | (second - first) gives the same set.

Show solution

The words in exactly one sentence are those in the union but not in the intersection: (first | second) - (first & second). By the rule for the size of a union there are 7 words in the union and 3 in the intersection, so \({7 - 3 = 4}\). They are 'cat', 'dog', 'log' and 'mat'. The other way round, first - second is {'cat', 'mat'} and second - first is {'dog', 'log'}, and their union is the same four words: the two outer regions of the Venn diagram.

Summary card

A dictionary finds a value by its key; a set keeps keys alone.

The anchor figure again: the name score_of points to a dictionary drawn as a two-column table of names and scores, with the row 'Chen' 80 highlighted as the result of score_of['Chen'], and a lookup of 'Femi' that fails with KeyError.
P01.6.3-F1

Look up by key, not by position.

  • Dictionary: d[k] looks up (a KeyError if missing), d[k] = v adds or replaces, k in d and d.get(k, x) are the safe tests.
  • Counting: counts[x] = counts.get(x, 0) + 1, or Counter(items).
  • Set: no duplicates, no order, a quick in; |, & and - give union, intersection and difference.
  • Size of a union: \({\abs{A \cup B}}\) is \({\abs{A} + \abs{B}}\) minus \({\abs{A \cap B}}\).

Sources and next steps

Sources. Each was opened and checked against this lesson on 12 November 2026.

  • Python Software Foundation, The Python Tutorial, section 5.5, Dictionaries: keys can be any immutable type, lists cannot be keys, a missing key raises KeyError while get returns None or a default, list(d) gives the keys in insertion order and in tests a key. Section 5.4, Sets: no duplicates, membership testing, and set(), not {}, for an empty set. Section 5.6: items().
  • Python Software Foundation, The Python Standard Library, Built-in Types: a dictionary preserves insertion order (guaranteed since version 3.7) and get never raises KeyError; Set Types: a set is an unordered collection of distinct hashable objects, with |, & and -.
  • Python Software Foundation, collections: Counter objects: a dict subclass for counting hashable objects, a count of zero for missing items, and most_common.
  • Python Software Foundation, Time complexity of operations on built-in types: x in s takes constant time on average for a set and a dictionary, and time in proportion to the length for a list. The Design and History FAQ explains why: a key’s hash, which depends on a per-process seed, gives the place to look.
  • Allen B. Downey, Think Python, 3rd edition, chapter 10, Dictionaries: 10.1 (a mapping from keys to values; a missing key is a KeyError), 10.3 (in checks keys, not values), 10.4 (a collection of counters), 10.5 (a loop visits the keys) and 10.6 (a list cannot be a key, because keys must be hashable). Free under CC BY-NC-SA 4.0.
  • scikit-learn User Guide, version 1.9.1, section 8.2.3, Text feature extraction: every term gets a unique whole-number index, stored in the vectorizer’s dictionary vocabulary_.

Next. P01.6.4 Sharing, copying and nesting shows what the arrows from names to objects mean when two names share one list, and copies the study table safely.

This lesson unlocks

P01.6.4 Sharing, copying and nesting

Your progress is saved in this browser only. No account, no tracking.
Back to top