RizTech Academy logo
RizTech Academy
Data StructuresLesson 3 of 825 min

Dictionaries: the workhorse of Python

A list answers "what is at position 3?". A dictionary answers "what is the price of tea?" — and that is the question real programs ask far more often.

Dictionaries are the most used data structure in Python. JSON from an API is a dictionary. Configuration is a dictionary. Objects, underneath, are dictionaries. Learn these properly.

Making one

Keys and values, separated by colons:

prices = {"tea": 40, "coffee": 120, "juice": 80}

person = {
    "name": "Priya",
    "age": 28,
    "city": "Pune",
}

empty = {}

That trailing comma after the last pair is legal and worth the habit — adding a line later becomes a one-line change, which makes diffs readable.

Note what a dictionary buys you here. The same data as a list:

person = ["Priya", 28, "Pune"]
print(person[1])

person[1] means nothing to a reader. person["age"] explains itself. When your items have names rather than positions, use a dictionary.

Getting values

print(prices["tea"])     # 40

Ask for a key that is not there and Python stops:

print(prices["water"])
KeyError: 'water'

KeyError is one of the errors you will see most. It always means the same thing: that key is not in that dictionary. Usually a typo, a different capitalisation, or data that did not contain what you assumed.

.get() — the one to reach for

print(prices.get("water"))           # None — no error
print(prices.get("water", 0))        # 0    — your own default

.get() returns None rather than raising, and takes a fallback as a second argument.

Use [ ] when a missing key means something has genuinely gone wrong and you want to know immediately. Use .get() when absence is normal — an optional field, a setting the user did not provide.

middle_name = person.get("middle_name", "")

That is better than an if around it, and much better than a KeyError in production.

Adding and changing

prices["water"] = 20        # adds it
prices["tea"] = 45          # changes it

Same syntax for both, which is convenient and occasionally hazardous — a typo in a key silently creates a new entry rather than updating the one you meant:

prices["coffe"] = 130       # a new entry, not a correction

No error. Just a dictionary with a wrong key in it and a bug somewhere later.

Removing:

del prices["water"]                  # KeyError if absent
value = prices.pop("water")          # removes and returns
value = prices.pop("water", None)    # with a fallback, cannot raise
prices.clear()                       # empties it

Checking membership

print("tea" in prices)         # True
print("water" not in prices)   # True

in checks keys, not values. To check values:

print(40 in prices.values())

Checking a key is fast no matter how large the dictionary is — that is the whole point of the structure. Checking values is a scan, so it gets slower as the dictionary grows.

Looping

Three ways, and picking the right one makes the code read properly:

for key in prices:
    print(key)

for value in prices.values():
    print(value)

for key, value in prices.items():
    print(f"{key}: {value}")

The last is the one you want most of the time.

for item, cost in prices.items():
    print(f"{item:<10} ₹{cost}")
tea        ₹45
coffee     ₹120
juice      ₹80

Looping over the dictionary itself gives you keys, which surprises people expecting values. Since Python 3.7, dictionaries keep insertion order, so the order you get is the order you added things.

Counting things

The single most common dictionary pattern:

text = "hello world"
counts = {}

for letter in text:
    if letter in counts:
        counts[letter] += 1
    else:
        counts[letter] = 1

print(counts)
{'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}

The if/else exists because counts[letter] += 1 on a key that does not exist raises KeyError — you cannot add one to something that is not there.

.get() collapses it to one line:

for letter in text:
    counts[letter] = counts.get(letter, 0) + 1

Read it as: take the current count or zero, add one, store it back. This shape appears constantly — tallying words, grouping records, counting errors.

There is a purpose-built tool too:

from collections import Counter
print(Counter("hello world"))

Worth knowing it exists. Write the .get() version a few times first, so the shortcut is a convenience rather than magic.

Grouping

The same idea, collecting into lists:

people = [
    ("Priya", "Pune"),
    ("Arjun", "Delhi"),
    ("Sneha", "Pune"),
]

by_city = {}
for name, city in people:
    by_city.setdefault(city, []).append(name)

print(by_city)
{'Pune': ['Priya', 'Sneha'], 'Delhi': ['Arjun']}

setdefault(key, []) returns the existing list, or inserts an empty one and returns that. Either way you get a list to append to.

It reads oddly the first time. The longer form is clearer while you are learning:

for name, city in people:
    if city not in by_city:
        by_city[city] = []
    by_city[city].append(name)

What can be a key

Keys must be immutable: strings, numbers, booleans, tuples. Values can be anything.

valid = {"a": 1, 42: "x", (1, 2): "point"}

invalid = {[1, 2]: "nope"}
TypeError: unhashable type: 'list'

A list can change, and a key that changes would get lost — the dictionary would no longer be able to find it. unhashable is the word Python uses for "cannot be a key", and it is a hint to use a tuple.

Keys are also case- and type-sensitive:

d = {"Name": 1}
print(d.get("name"))    # None — different key

d = {1: "int", "1": "string"}    # two distinct keys

Merging

defaults = {"theme": "dark", "size": 14}
user = {"size": 16}

settings = {**defaults, **user}
print(settings)
{'theme': 'dark', 'size': 16}

Later wins, so user settings override defaults. defaults | user does the same on Python 3.9 and above, and defaults.update(user) modifies defaults in place.

Practice

  1. Build a dictionary of five countries and their capitals. Print one, add one, change one, delete one.
  2. Cause a KeyError, then rewrite the same line with .get() and a sensible default.
  3. Count the letters in "mississippi" using the .get() pattern. Then find which letter appears most.
  4. Count words instead of letters in a sentence. You will need .split().
  5. Given a list of (name, department) pairs, group the names by department.
  6. Store a student's marks as {"maths": 78, "science": 85, "english": 72}. Print the total, the average, and the subject with the highest mark.
  7. Build a small phone book with a menu loop: add, look up, delete, list all. Looking up a missing name must not crash.
  8. Try {[1, 2]: "x"}, read the error, then make it work with a tuple.

Next: tuples, which are the lists you are not allowed to change — and why that turns out to be useful.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship