Skip to main content

Command Palette

Search for a command to run...

Dictionaries, Explained Like You've Never Coded Before

Updated
9 min read

Lists answered "how do I hold many things in order?" Tuples answered "how do I lock that down?" Sets answered "how do I guarantee no duplicates and check membership fast?" Dictionaries answer a completely different question: "how do I look something up by name, instead of by position?"

Same approach as before — why first, then what, then code.


1. What is a dictionary?

Picture a real, physical dictionary — the book kind. You don't read it front to back to find a word. You flip straight to "M," scan a little, and land on "mango." You go directly to what you want using the word itself as the way in, not its page number.

That's exactly what a Python dictionary is:

person = {
    "name": "Aisha",
    "age": 23,
    "city": "Mumbai"
}

Instead of asking "what's at position 1?" the way a list would, you ask "what's stored under 'age'?" — and you go straight there.


2. Key-value storage

Every entry in a dictionary is a pair: a key (the lookup word) and a value (the definition behind it).

Analogy: Think of a row of labeled drawers in a filing cabinet. The label on the drawer is the key — "Tax Documents," "Photos," "Recipes." What's inside the drawer is the value. You never search drawer by drawer hoping to stumble on the right one; you read the label and go straight there.

From first principles, this solves a real limitation of lists: in a list, the only way to refer to something is its numeric position, which means you have to remember "the city is item 2" — fragile, and meaningless to anyone reading your code. A dictionary lets the meaning itself ("city") be the address.

There's one structural rule worth knowing immediately: keys must be hashable — the same requirement sets have, and for the identical reason. A dictionary uses the same fingerprint-style hashing trick to jump straight to a value instead of searching for it, so keys must be unchanging things like strings, numbers, or tuples. (This is also exactly why tuples — but never lists — can be dictionary keys, as we saw earlier.)

d = {(1, 2): "point A"}   # ✅ tuple key works
d2 = {[1, 2]: "point A"}  # ❌ TypeError: unhashable type: 'list'

3. Dictionary creation

empty = {}
person = {"name": "Aisha", "age": 23}

# Or, using the dict() constructor:
person2 = dict(name="Aisha", age=23)

# Or, from a list of pairs:
person3 = dict([("name", "Aisha"), ("age", 23)])

All three create the exact same kind of structure — different doors into the same room.


4. Accessing values

The most direct way is square brackets with the key:

person = {"name": "Aisha", "age": 23}
print(person["name"])   # "Aisha"

But what happens if the key doesn't exist?

print(person["email"])   # ❌ KeyError: 'email'

This is one of the most common beginner crashes. The fix is .get(), which asks politely instead of demanding:

print(person.get("email"))            # None  -> no crash
print(person.get("email", "N/A"))     # "N/A" -> your own fallback value

Analogy: person["email"] is like marching up to the filing cabinet and yanking out a drawer that may not exist — if it's not there, you fall over (the program crashes). person.get("email", "N/A") is like politely asking "is there a drawer labeled email? If not, just hand me this blank placeholder instead." Same goal, but one version is robust and the other isn't.


5. Updating values

person["age"] = 24            # change an existing key's value
person["country"] = "India"   # add a brand-new key, instantly

First-principles note: there's no real difference, internally, between "updating" and "adding" in a dictionary — both are just "set this key to point to this value." If the key already existed, its old value is overwritten; if not, a new entry is created. It's the same single operation either way.

You can also merge in many updates at once:

person.update({"age": 25, "job": "Engineer"})

Analogy: update() is like handing the filing clerk a small stack of new labels and contents and saying "swap in whatever matches, and file the rest as new" — in one motion, instead of one drawer at a time.


6. Removing values

del person["country"]          # removes the key entirely; no return value

age = person.pop("age")        # removes AND hands you the value back
print(age)                     # 25

person.popitem()               # removes and returns the LAST inserted key-value pair
person.clear()                 # empties the whole dictionary

Notice the symmetry with lists: pop() here behaves just like list.pop() did — it removes and returns, instead of silently deleting like del does. Once you've internalized that pattern once, it transfers everywhere in Python.


7. keys() — just the labels

person = {"name": "Aisha", "age": 23, "city": "Mumbai"}
print(person.keys())   # dict_keys(['name', 'age', 'city'])

Analogy: This is like sliding open the filing cabinet and reading every label on every drawer, without looking inside any of them.

You'll mostly use this when looping:

for key in person.keys():
    print(key)

(In fact, looping over a dictionary directly — for key in person: — does the exact same thing; .keys() is there mainly for clarity and for cases where you want to treat the keys as their own collection.)


8. values() — just the contents

print(person.values())   # dict_values(['Aisha', 23, 'Mumbai'])

Analogy: Now you're pulling every drawer's contents out onto the table, without caring what any label said.

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

9. items() — labels and contents, together

print(person.items())
# dict_items([('name', 'Aisha'), ('age', 23), ('city', 'Mumbai')])

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

Analogy: .items() is opening every drawer and reading the label at the same time — the complete picture, pair by pair. This is the version you'll reach for constantly, because most real tasks need both "which one is this" and "what's inside it" simultaneously. Notice that each pair comes back as a tuple — another quiet reminder of why tuples exist: a fixed two-piece unit (key, value) that shouldn't be reordered or resized.


10. Dictionary comprehensions

Just like list comprehensions compress a for-loop into one line, dictionary comprehensions do the same for building dictionaries.

The long way:

squares = {}
for n in range(5):
    squares[n] = n * n

The comprehension way:

squares = {n: n * n for n in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

Read it like a sentence: "for every n in range 5, the key is n and the value is n * n." Same structure as a list comprehension, just with a key: value pair up front instead of a single expression.

You can filter too:

even_squares = {n: n*n for n in range(10) if n % 2 == 0}

A genuinely useful real-world case — flipping keys and values:

original = {"a": 1, "b": 2, "c": 3}
flipped = {v: k for k, v in original.items()}
# {1: "a", 2: "b", 3: "c"}

11. Nested dictionaries

A value inside a dictionary can be... another dictionary. This is how you represent anything with real-world structure, like a database record or a JSON file.

students = {
    "Aisha": {"age": 23, "city": "Mumbai"},
    "Raj": {"age": 25, "city": "Delhi"}
}

print(students["Aisha"]["city"])   # "Mumbai"

Analogy: Recall the nested-lists example from earlier — a school made of classrooms made of students. A nested dictionary is the same idea, but every level now has labels instead of numbered seats. Instead of "go to classroom 1, then seat 2," it's "go to the drawer labeled Aisha, then inside that drawer, go to the drawer labeled city." Nesting dictionaries is, in fact, exactly how most real-world data (think: API responses, configuration files, JSON documents) is naturally represented — because real-world data is rarely flat.


12. Ordered dictionaries

This one needs a small history lesson to make first-principles sense.

In old versions of Python (before 3.7), regular dictionaries made no promise about the order you'd get back when looping through them — internally, items were stored based on their hash, not their insertion order. If you genuinely needed order preserved, you had to explicitly use a special tool: OrderedDict, from the collections module.

from collections import OrderedDict

od = OrderedDict()
od["first"] = 1
od["second"] = 2
od["third"] = 3
# guaranteed to iterate in this exact order

Then something changed: starting in Python 3.7, regular dictionaries themselves started guaranteeing insertion order, as an official language feature rather than an implementation accident. So today, in modern Python:

d = {"first": 1, "second": 2, "third": 3}
print(list(d.keys()))   # ['first', 'second', 'third']  -> order preserved, no OrderedDict needed

So why does OrderedDict still exist, if regular dicts kept order now? A couple of practical reasons:

  • It has a few extra order-specific methods regular dicts don't, like move_to_end().

  • Equality comparisons differ: two OrderedDicts are only considered equal if their order matches too, not just their contents — while two regular dicts are equal as long as their key-value pairs match, regardless of order.

  • It signals intent clearly in code you're reading — "order genuinely matters here" — even though a plain dict would technically also preserve it now.

Analogy: It's like the difference between a normal guest book (which, these days, conveniently happens to record guests in the order they signed) versus a guest book that was specifically designed and certified to track order, with extra features for re-arranging entries and stricter rules about what counts as "the same guest book." Both keep order today — but only one was built around that guarantee from day one.


Tying it together

Lists give you order without meaning. Dictionaries give you meaning without caring about position. Every key-value pair is really just answering: "If I ask for this name, what comes back?" — which is, when you strip away the syntax, the exact same question a real dictionary, a filing cabinet, or a phonebook has always answered. Python didn't invent a new idea here; it just made an old, intuitive one programmable.