TypeError: unhashable type: 'dict' (tested fix)

TypeError: unhashable type: 'dict' means a dict was used as a set member, a dict key, or a cached function argument. Dicts are mutable, so they cannot be hashed. We reproduced each case and tested the fixes on Python 3.13.

1. Cause: a dict used where a hashable value is needed

rows = [{"id": 1, "tag": "a"}, {"id": 1, "tag": "a"}, {"id": 2, "tag": "b"}]
for label, fn in [("set(rows)", lambda: set(rows)),
                  ("{row: 1}", lambda: {rows[0]: 1}),
                  ("row in set()", lambda: rows[0] in set()),
                  ("dict.fromkeys(rows)", lambda: dict.fromkeys(rows))]:
    try:
        fn()
    except TypeError as e:
        print(f"{label:20} -> TypeError: {e}")
print("row in list:", rows[0] in rows)   # lists compare with ==, no hashing

Output:

set(rows)            -> TypeError: unhashable type: 'dict'
{row: 1}             -> TypeError: unhashable type: 'dict'
row in set()         -> TypeError: unhashable type: 'dict'
dict.fromkeys(rows)  -> TypeError: unhashable type: 'dict'
row in list: True

Set members, dict keys and x in some_set lookups all hash the value, and a dict is mutable, so it has no hash. The usual trigger is a list of dicts from JSON or a database (an API response, csv.DictReader rows) passed to set() to deduplicate it. Membership in a list works because lists compare with == and never hash.

2. Fix: deduplicate on a hashable key

import json
rows = [{"id": 1, "tag": "a"}, {"tag": "a", "id": 1}, {"id": 2, "tag": "b"}]
# Fix 1: dedupe on a hashable key built from the dict, keeping order
seen, unique = set(), []
for r in rows:
    k = tuple(sorted(r.items()))
    if k not in seen:
        seen.add(k); unique.append(r)
print(unique)
# Fix 2: nested values (lists/dicts inside) -> use a canonical JSON string
nested = [{"id": 1, "tags": ["x"]}, {"tags": ["x"], "id": 1}]
print(len({json.dumps(r, sort_keys=True) for r in nested}))
try:
    {tuple(sorted(r.items())) for r in nested}
except TypeError as e:
    print("tuple(items) with a list inside -> TypeError:", e)

Output:

[{'id': 1, 'tag': 'a'}, {'id': 2, 'tag': 'b'}]
1
tuple(items) with a list inside -> TypeError: unhashable type: 'list'

tuple(sorted(d.items())) is hashable and ignores key order, so the first two rows count as the same row. It only works when every value is itself hashable; with a list or dict inside, it fails with unhashable type: 'list'. For nested data, json.dumps(d, sort_keys=True) gives a canonical string key.

3. lru_cache and other caches with dict arguments

from functools import lru_cache
@lru_cache
def price(opts):
    return len(opts)
try:
    price({"size": "L"})
except TypeError as e:
    print("lru_cache ->", f"TypeError: {e}")
print("frozenset fix:", price(frozenset({"size": "L"}.items())))

Output:

lru_cache -> TypeError: unhashable type: 'dict'
frozenset fix: 1

functools.lru_cache builds its cache key by hashing the arguments, so passing a dict raises the same error. Pass a frozenset(d.items()) or a tuple instead (and rebuild the dict inside the function if needed).

Related: unhashable type: 'list' (all causes) ยท KeyError.

Frequently asked questions

Why is a dict unhashable in Python?

Dicts are mutable, so their hash could change; Python refuses to hash them. Sets, dict keys and lru_cache all need hashable values.

How do I remove duplicate dicts from a list?

Track a hashable key such as tuple(sorted(d.items())), or json.dumps(d, sort_keys=True) for nested values, in a set while you loop.

How do I use a dict with lru_cache?

Pass frozenset(d.items()) or a tuple instead of the dict; lru_cache hashes every argument.

Written with AI assistance. Measured on 2026-10-23 with Python 3.13.5 on Debian: set(), dict keys, set membership and dict.fromkeys with dicts, two dedupe fixes, and functools.lru_cache with a dict argument. Every block was run verbatim and its output pasted unedited. PlainHeaders is our extension; Python is free.