TypeError: unhashable type: 'list' (tested fixes)

TypeError: unhashable type: 'list' means a list ended up somewhere Python needs to hash it. We ran every common trigger on Python 3.13.5, including two that don't look like they store anything, and tested the fix for each.

1. Every place it comes from

tests = {
    "dict key":      lambda: {["a", "b"]: 1},
    "set item":      lambda: {[1, 2]},
    "set() of rows": lambda: set([[1, 2], [3, 4]]),
    "in a set":      lambda: [1] in {(1,)},
    "dict lookup":   lambda: {"a": 1}[["a"]],
    "dict in set":   lambda: {{"id": 1}},
    "set in set":    lambda: {{1, 2}},
}
for name, f in tests.items():
    try:
        f()
        print(f"{name}: ok")
    except TypeError as e:
        print(f"{name}: {e}")
dict key: unhashable type: 'list'
set item: unhashable type: 'list'
set() of rows: unhashable type: 'list'
in a set: unhashable type: 'list'
dict lookup: unhashable type: 'list'
dict in set: unhashable type: 'dict'
set in set: unhashable type: 'set'

Measured: anything that puts a value into a hash table raises it: a dict key, a set element, set(list_of_lists), and also looking up a list ([1] in some_set, d[["a"]]), which surprises people because nothing is being stored. Lists, dicts and sets are mutable, so Python refuses to hash them: if the value changed after insertion, the table could never find it again. The type in quotes tells you which one reached the hash table: 'list', 'dict' or 'set'.

2. A tuple doesn't help if it contains a list

nested = (1, [2, 3])
try:
    {nested: "x"}
except TypeError as e:
    print(e)
print(hash((1, (2, 3))) == hash((1, (2, 3))))
unhashable type: 'list'
True

Measured: (1, [2, 3]) is a tuple but hashing it hashes each item, so the inner list raises the same error. Convert all the way down: (1, (2, 3)) works.

3. Fix: de-duplicate rows and records

rows = [[1, "a"], [2, "b"], [1, "a"]]
print(list(dict.fromkeys(map(tuple, rows))))

records = [{"id": 1, "tags": ["x"]}, {"id": 1, "tags": ["x"]}]
seen = set()
unique = []
for r in records:
    key = (r["id"], tuple(r["tags"]))
    if key not in seen:
        seen.add(key)
        unique.append(r)
print(unique)
print(frozenset({1, 2}) in {frozenset({2, 1})})
[(1, 'a'), (2, 'b')]
[{'id': 1, 'tags': ['x']}]
True

Measured: dict.fromkeys(map(tuple, rows)) removes duplicate list rows and keeps their original order (a plain set would lose the order). For dicts, build a hashable key from the fields that define “the same record” and keep the original objects in a list. frozenset is the hashable version of set, and two frozensets with the same items compare and hash equal regardless of order.

4. Fix: @lru_cache / @cache with list arguments

from functools import lru_cache

@lru_cache
def total(items):
    return sum(items)

print(total((1, 2, 3)))
try:
    total([1, 2, 3])
except TypeError as e:
    print(e)
6
unhashable type: 'list'

Measured: functools.lru_cache builds a dict key from the arguments, so a list argument raises unhashable type: 'list' at call time, even though the function body would happily sum a list. Pass a tuple, or convert inside a small wrapper before calling the cached function.

5. Counter, and a list used as a key path

from collections import Counter

words = [["the", "cat"], ["the", "cat"], ["a", "dog"]]
try:
    Counter(words)
except TypeError as e:
    print(e)
print(Counter(map(tuple, words)).most_common(1))

data = {"user": {"name": "ann"}}
key = ["user", "name"]
try:
    data[key]
except TypeError as e:
    print(e)
d = data
for k in key:
    d = d[k]
print(d)
unhashable type: 'list'
[(('the', 'cat'), 2)]
unhashable type: 'list'
ann

Measured: Counter is a dict, so counting lists fails the same way; map to tuples first. The second case is a common slip: a list of keys meant as a path (["user", "name"]) passed as one key. The fix is to walk the path, not to hash it.

What not to do

Using str(my_list) as the key also stops the error, but keys then stop following equality: [1] == [1.0] is true, yet str([1]) is '[1]' and str([1.0]) is '[1.0]', while the tuple keys (1,) and (1.0,) find each other. You also can't get the list back out of the string. Convert to a tuple (or a frozenset when order doesn't matter) instead. And don't add a __hash__ to a mutable class just to silence the error: if the object changes after insertion, set and dict lookups silently stop finding it.

Got unhashable type: 'dict' instead? See unhashable type: 'dict' (dedupe a list of dicts, lru_cache with dict arguments).

Frequently asked questions

What does TypeError: unhashable type: 'list' mean?

A list was used where Python needs a hash: as a dict key, a set element, or the value you look up with 'in' on a set or dict. We measured all of these raising the same message on Python 3.13.5.

How do I fix unhashable type: 'list'?

Convert the list to a tuple: tuple(x), or map(tuple, rows) for a list of lists. If the tuple itself contains lists, convert those too; we measured that (1, [2, 3]) still raises.

How do I remove duplicate lists from a list?

list(dict.fromkeys(map(tuple, rows))). We measured that it removes duplicates and keeps the original order.

What does unhashable type: 'dict' mean?

A dict was used as a set element or dict key. Build a tuple of the fields that identify the record and use that as the key instead, keeping the dicts themselves in a list.

Why does lru_cache raise unhashable type: 'list'?

lru_cache stores results in a dict keyed by the arguments. We measured that a tuple argument works and a list argument raises at call time; pass tuples to cached functions.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5: lists, dicts and sets used as dict keys, set elements, set() input and in-lookups; a tuple containing a list; functools.lru_cache with list arguments; collections.Counter over lists; and the de-duplication fixes. Every code block on this page was run verbatim and its output pasted from that run. PlainHeaders is our extension; everything else here is built-in Python.