Skip to content
Happy Programming Guide
Start learning
Python

Mastering Python: Solving Dict TypeError

The five TypeErrors dictionaries throw — unhashable type, not subscriptable, not callable, missing arguments and unsupported operand — and the fix for each.

An open laptop on a wooden desk

Dictionaries produce a small, predictable set of TypeErrors, and each message points at a specific mistake. This guide covers the five you will actually meet: unhashable keys, subscripting something that is not a dictionary, calling a dictionary like a function, the dict() constructor being given the wrong shape, and trying to merge with +.

1. unhashable type: ‘list’#

Python
data = {}
data[["a", "b"]] = 1
Output
TypeError: unhashable type: 'list'

Dictionary keys must be hashable, which in practice means they must not be able to change. A list can change, so its hash would change, and the dictionary would lose track of where it stored the value.

Use a tuple instead:

Python
data[("a", "b")] = 1        # tuples are immutable
data[frozenset({"a", "b"})] = 2   # when order should not matter

The same error appears when you put a list into a set, or use one as a key indirectly:

Python
seen = set()
seen.add([1, 2])            # TypeError
seen.add((1, 2))            # fine

A tuple only counts as hashable if everything inside it is:

Python
key = (1, [2, 3])
hash(key)                   # TypeError: unhashable type: 'list'

2. ‘NoneType’ object is not subscriptable#

Python
def load_config():
    print("loading")        # forgot to return


config = load_config()
print(config["host"])
Output
TypeError: 'NoneType' object is not subscriptable

“Subscriptable” means “can be indexed with square brackets”. None cannot. The real bug is that something gave you None where you expected a dictionary — a function with a missing return, a failed lookup, or a JSON field that was absent.

Python
config = load_config()

if config is None:
    raise ValueError("Configuration could not be loaded")

print(config["host"])

The variation 'dict' object is not subscriptable does not exist — but 'method' object is not subscriptable does, and it means you forgot brackets:

Python
print(person.keys[0])       # TypeError
print(list(person.keys())[0])   # correct

3. ‘dict’ object is not callable#

Python
person = {"name": "Ana"}
print(person("name"))
Output
TypeError: 'dict' object is not callable

Round brackets call something; square brackets look something up. Dictionaries use square brackets.

There is a nastier version of this. If you name a variable dict, you replace the built-in:

Python
dict = {"a": 1}         # shadows the built-in type
other = dict()          # TypeError: 'dict' object is not callable

The same applies to list, set, str, id and type. Name the variable something descriptive and the problem disappears.

4. dict expected at most 1 argument#

Python
d = dict("a", 1)
Output
TypeError: dict expected at most 1 argument, got 2

The constructor takes keyword arguments, a mapping, or an iterable of pairs — not two loose values:

Python
d = {"a": 1}
d = dict(a=1)
d = dict([("a", 1), ("b", 2)])
d = dict(zip(["a", "b"], [1, 2]))

Note that dict(a=1) only works when the key is a valid Python identifier. For a key like "first name" you need the literal form or the pairs form.

5. unsupported operand type(s) for +: ‘dict’ and ‘dict’#

Python
merged = {"a": 1} + {"b": 2}
Output
TypeError: unsupported operand type(s) for +: 'dict' and 'dict'

Three ways to merge, all correct:

Python
a = {"x": 1, "y": 2}
b = {"y": 99, "z": 3}

merged = {**a, **b}      # any Python 3.5+
merged = a | b           # Python 3.9+
merged = dict(a); merged.update(b)   # in place

In all three, later values win: merged["y"] is 99. That is what makes this the standard pattern for layering defaults under user settings:

Python
settings = {**DEFAULTS, **user_settings}

Reading any TypeError in three steps#

  1. Read the type names in the message. “NoneType” tells you something returned nothing; “list” in an unhashable message tells you which value is the problem.
  2. Look at the last line of the traceback that is in your own file. Library frames below it are usually consequences, not causes.
  3. Print the type, not the value. print(type(config), config) settles it immediately, and often the value alone looks fine.

Bonus: KeyError is not a TypeError#

Python
person = {"name": "Ana"}
print(person["email"])      # KeyError: 'email'

KeyError means the dictionary is fine but the key is not in it. Use .get() with a default, or check with in first — and only when a missing key is genuinely acceptable.

Questions people ask#

Why must dictionary keys be immutable?

The dictionary stores each entry in a slot chosen by the key’s hash. If the key changed afterwards, its hash would point somewhere else and the value would be unreachable. Forbidding mutable keys prevents that entirely.

Can I use an object as a key?

Yes, if it is hashable. Custom classes are hashable by default, using identity. If you define __eq__ you must also define __hash__, or instances become unhashable.

What is the difference between TypeError and ValueError?

TypeError means the kind of thing is wrong — a list where a string was needed. ValueError means the type is right but the content is not, such as int("abc").

How do I merge dictionaries deeply?

Neither | nor {**a, **b} merges nested dictionaries — a nested value in the second simply replaces the first. For deep merging you need a small recursive function, or a library built for it.

Where to go next#

Python dictionaries explained, from keys to mergingRead next

Keep reading

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *