---
title: Dictionaries in Python
source: https://app.sythra.ai/learn/python/dictionaries-in-python
topic: Python
updated: 2026-08-12
publisher: Sythra (https://app.sythra.ai)
---

# Dictionaries in Python

A Python dictionary stores key-value pairs and looks up values by key almost instantly using a hashtable. Keys must be immutable (strings, numbers, tuples) — lists can never be keys, though they can be values.

_Source: [https://app.sythra.ai/learn/python/dictionaries-in-python](https://app.sythra.ai/learn/python/dictionaries-in-python) — free to read on Sythra._

## Key points

- Dictionaries map keys to values — d['key'] looks up almost instantly
- Missing keys raise KeyError; use .get(key, default) to avoid that
- The histogram pattern counts things using a dictionary
- Only hashable (immutable) types can be dictionary keys — never lists
- Memoization uses a dictionary to cache results and speed up recursion
- Reassigning a global inside a function needs the global keyword

A list has one frustrating limitation: the **only** way to find something inside it is by position. What if you want to look up a _word_ and get back its _meaning_, or a _name_ and get back a _phone number_? Searching a list item by item for that would be slow — and this is exactly why **dictionaries** exist.

A Python dictionary stores **key-value pairs**, and looking something up by its key is nearly instant, no matter how large the dictionary grows. This page covers creating and using dictionaries, the histogram counting pattern, why lists can never be keys, and how dictionaries power a genuinely powerful optimization trick called memoization. Pair it with [Lists in Python](/learn/python/lists-in-python) for the sequence type dictionaries are usually compared against.

## What you will learn

By the end you can:

- Create dictionaries and look up, add, and delete key-value pairs
- Use the **histogram** pattern to count things with a dictionary
- Loop through a dictionary's keys, values, and items
- Explain why lists can never be dictionary keys
- Understand **memoization** and how to work with global variables safely

## Creating and using dictionaries

A dictionary is a **mapping** between keys and values. Each key maps to exactly one value; a key and its value together are called a **key-value pair**, or sometimes an **item**. Create an empty dictionary with `dict()` or empty curly braces:

```python
eng2sp = dict()
print(eng2sp)
# {}
```

To add a key-value pair, use square brackets on the left of an assignment — almost like indexing into an empty slot:

```python
eng2sp['one'] = 'uno'
print(eng2sp)
# {'one': 'uno'}
```

You can also build a dictionary with several items already inside, all at once:

```python
eng2sp = {'one': 'uno', 'two': 'dos', 'three': 'tres'}
```

> **Insertion order:** 

Look up a value using its key in square brackets. If the key does not exist, Python immediately raises a `KeyError` — it never silently returns `None`:

```python
print(eng2sp['two'])
# 'dos'

print(eng2sp['four'])
# KeyError: 'four'
```

A few other handy operations:

```python
len(eng2sp)        # 3 — number of key-value pairs

'one' in eng2sp    # True — checks keys, not values
'uno' in eng2sp    # False — 'uno' is a value, not a key

vals = eng2sp.values()
'uno' in vals      # True — check membership in values instead
```

`in` is dramatically faster on dictionaries than on lists. A list check has to scan every element one at a time; a dictionary uses a **hashtable** that finds the answer in roughly the same tiny amount of time whether the dictionary has 10 items or 10 million.

## Counting with a dictionary (the histogram pattern)

One of the most useful things you can do with a dictionary is count how often something appears. The clean, idiomatic way to count letters in a string: use a dictionary where keys are letters and values are their counts — you never have to know in advance which letters will show up.

```python
def histogram(s):
    d = dict()
    for c in s:
        if c not in d:
            d[c] = 1
        else:
            d[c] += 1
    return d

h = histogram('brontosaurus')
print(h)
# {'b': 1, 'r': 2, 'o': 2, 'n': 1, 't': 1, 's': 2, 'a': 1, 'u': 2}
```

"Histogram" is a statistics term meaning a set of counters — exactly what this is, a frequency map of every character in the string.

### A cleaner version with .get()

`.get()` returns a **default value** when a key does not exist, instead of raising a `KeyError` — this eliminates the `if`/`else`:

```python
h.get('a', 0)   # 1 — 'a' exists
h.get('b', 0)   # 0 — 'b' doesn't exist, returns the fallback

def histogram(s):
    d = dict()
    for c in s:
        d[c] = d.get(c, 0) + 1
    return d
```

`d.get(c, 0) + 1` reads as "give me the current count for `c`, or 0 if it isn't there yet, then add 1." This pattern shows up constantly in real Python code.

## Looping through a dictionary

A `for` loop over a dictionary loops through its **keys**:

```python
def print_hist(h):
    for c in h:
        print(c, h[c])
```

For a guaranteed order, sort the keys yourself. `.keys()` returns a view object in Python 3, not a plain list, so wrap it in `sorted()`:

```python
def print_hist_sorted(h):
    keys = sorted(h.keys())
    for k in keys:
        print(k, h[k])
```

## Reverse lookup

A normal lookup gives you the value for a key. A **reverse lookup** is the opposite — you have a value, and want the key that maps to it. There is no built-in shortcut; you search manually:

```python
def reverse_lookup(d, v):
    for k in d:
        if d[k] == v:
            return k
    raise ValueError('value does not appear in the dictionary')

h = histogram('parrot')
reverse_lookup(h, 2)   # 'r' — 'r' appears twice
reverse_lookup(h, 3)   # ValueError: value does not appear in the dictionary
```

You can raise exceptions yourself, on purpose, using `raise` — this is how well-written functions communicate failure clearly instead of silently returning a wrong answer. Reverse lookups are genuinely slower than forward lookups, since there is no hashtable shortcut going in that direction — check every value, one at a time.

## Why lists can't be dictionary keys

Dictionary values can be anything — including lists, which is useful for grouping multiple values under one key. But lists can **never** be keys:

```python
t = [1, 2, 3]
d = dict()
d[t] = 'oops'
# TypeError: unhashable type: 'list'
```

Dictionaries work internally using a **hashtable**, which computes a numeric fingerprint (a **hash**) for each key to decide where to store it. That hash must never change after storage. Immutable types — strings, integers, tuples — always have a stable hash. Mutable types like lists could change after being stored, breaking the whole system. The rule: **only hashable (immutable) types can be dictionary keys.** Tuples work fine as keys; lists and other dictionaries do not.

## Memoization: teaching a function to remember

A plain recursive Fibonacci function recalculates the same values over and over — `fibonacci(2)` might get computed dozens of times inside a single call, and it gets exponentially worse for larger inputs. **Memoization** fixes this by storing results you have already worked out, using a dictionary:

```python
known = {0: 0, 1: 1}

def fibonacci(n):
    if n in known:
        return known[n]
    res = fibonacci(n-1) + fibonacci(n-2)
    known[n] = res
    return res

fibonacci(10)    # 55
fibonacci(50)    # 12586269025 — still fast
```

`known` stores every Fibonacci number already computed. Each call first checks `known`; if the answer is there, it returns immediately with no further work. Otherwise it computes it, saves it, and returns it. Now each value only ever gets computed once — a dictionary is the perfect tool for this.

## Global variables

In the memoization example, `known` is defined outside any function — a **global variable**, which persists for the entire program's lifetime rather than disappearing when a function returns. Any function can freely **read** a global with no special syntax:

```python
verbose = True

def example1():
    if verbose:
        print('Running example1')
```

Reassigning a global from inside a function is where the trap lives:

```python
been_called = False

def example2():
    been_called = True   # WRONG — creates a new local variable instead

example2()
print(been_called)
# False — nothing actually changed
```

Python assumes any assignment inside a function creates a new **local** variable — it never touches the global one. To genuinely modify a global, use the `global` keyword:

```python
been_called = False

def example2():
    global been_called
    been_called = True   # now it actually works

example2()
print(been_called)
# True
```

> **Exception: mutable globals:** 

## Useful dictionary methods

`.keys()`, `.values()`, and `.items()` give you views over a dictionary's contents. `.items()` is especially useful for looping over both the key and value together:

```python
d = {'a': 1, 'b': 2, 'c': 3}

for key, value in d.items():
    print(key, 'maps to', value)
```

`.pop()` removes a key and returns its value:

```python
d = {'a': 1, 'b': 2}
val = d.pop('a')
print(val)   # 1
print(d)     # {'b': 2}
```

`.update()` merges another dictionary in, overwriting shared keys:

```python
d1 = {'a': 1, 'b': 2}
d2 = {'b': 99, 'c': 3}
d1.update(d2)
print(d1)   # {'a': 1, 'b': 99, 'c': 3}
```

`.setdefault()` sets a key only if it is not already there — the same pattern used inside `histogram` earlier:

```python
d = {'a': 1}
d.setdefault('a', 99)   # 'a' exists — no change, returns 1
d.setdefault('b', 99)   # 'b' is new — sets it to 99, returns 99
print(d)   # {'a': 1, 'b': 99}
```

## Dictionary comprehensions (a preview)

Like list comprehensions, Python has a compact way to build a dictionary in one line:

```python
words = ['apple', 'banana', 'cherry']
lengths = {word: len(word) for word in words}
print(lengths)
# {'apple': 5, 'banana': 6, 'cherry': 6}
```

The general shape is `{key_expr: value_expr for item in iterable}` — the same logic as a loop that builds up a dictionary, written as a single expression.

## Common mistakes

- Assuming `'value' in d` checks values — `in` checks keys by default
- Using a list as a dictionary key and hitting `TypeError: unhashable type`
- Reassigning a global inside a function without `global`, silently creating a local instead
- Reading and writing the same global in one line (`count = count + 1`) without declaring `global count` first
- Expecting `.keys()` in Python 3 to behave like a plain list — it's a view, wrap it in `sorted()` or `list()`

## Why this matters

Dictionaries are the workhorse of real Python programs — counting, caching, configuration, JSON data, and anything shaped like "look this up by name." Understanding hashable keys and mutability here prevents bugs that only show up once your data gets large or your recursion gets deep.

> **Practice with Sythra:**  [Practice with AI tutor](https://app.sythra.ai/pricing)

## FAQ

### What is a dictionary in Python?

A dictionary is a mapping of key-value pairs. You look up a value using its key, e.g. d['one'], and lookups are nearly instant regardless of how large the dictionary is, thanks to an internal hashtable.

### Why can't you use a list as a dictionary key in Python?

Dictionary keys must be hashable, meaning their hash value never changes. Lists are mutable and can change after creation, which would break the hashtable, so Python raises TypeError: unhashable type: 'list'.

### What is the difference between dict.get() and d[key] in Python?

d[key] raises a KeyError if the key doesn't exist. d.get(key, default) returns a fallback value instead of crashing, making it safer when a key might be missing.

### What is memoization in Python?

Memoization is caching the results of expensive function calls (often recursive ones like Fibonacci) in a dictionary, so repeated calls with the same input return instantly instead of recalculating.

### How do you modify a global variable inside a Python function?

Declare it with the global keyword inside the function first, e.g. global count, then assign to it. Without global, an assignment creates a new local variable instead of changing the global one.

### How do you loop through a dictionary's keys and values together?

Use .items(), e.g. for key, value in d.items(): print(key, value). This gives you both the key and its value on each pass, instead of only the key.

## Related

- [Lists in Python](https://app.sythra.ai/learn/python/lists-in-python) — The mutable sequence type dictionaries are usually compared against.
- [Strings in Python](https://app.sythra.ai/learn/python/strings-in-python) — Why strings are hashable and can be used as dictionary keys.
- [Data Types in Python](https://app.sythra.ai/learn/python/data-types) — Where dict fits among Python's mutable, non-primitive types.
- [Types, Values, and Errors in Python](https://app.sythra.ai/learn/python/types-values-errors) — More on KeyError, TypeError, and how Python errors work.
- [Tuples in Python](https://app.sythra.ai/learn/python/tuples-in-python) — Immutability, unpacking, *args, zip(), and DSU sorting.
- [Sets in Python](https://app.sythra.ai/learn/python/sets-in-python) — Uniqueness, fast membership checks, and set math.
- [Python course hub](https://app.sythra.ai/learn/python) — All free Python explainers and the path into Agentic practice.
- [More Tools for Iteration in Python](https://app.sythra.ai/learn/python/iteration-tools-python) — continue, enumerate(), zip(), comprehensions, and generators.
- [Classes and Objects in Python](https://app.sythra.ai/learn/python/classes-and-objects-python) — Defining classes, attributes, embedded objects, and copying.
- [File Handling in Python (Reading, Writing, CSV & JSON)](https://app.sythra.ai/learn/python/file-handling-python) — open(), with, reading modes, CSV, and JSON.

---

Written by Sythra — Learn machine learning by building. Practice this topic with Sythra's AI tutor: https://app.sythra.ai/pricing
