Python raises TypeError: unhashable type: 'list' when you use a list where Python needs a hashable value: as a dictionary key, as an element of a set, in an in check against a set or dict, or as an argument to a function decorated with @lru_cache. Lists can change after they are created, so they have no stable hash. The usual fix is to convert the list to a tuple (or a frozenset when order does not matter) before you use it as a key.
Dicts and sets find items by their hash, and the Python glossary entry for hashable says an object is hashable only if its hash never changes during its lifetime. A list can grow, shrink or change at any moment, so Python refuses to hash it.
Every traceback and output block below comes from real runs on my machine with Python 3.13.5. I also ran the key examples on Python 3.10, 3.12 and 3.14.8, because Python 3.14 rewords this error, and that new wording is worth knowing when you search for it.
In this guide:
- What "unhashable type: 'list'" actually means
- Cause 1: Using a list as a dictionary key
- Cause 2: Putting lists inside a set literal
- Cause 3: Calling set.add() with a list
- Cause 4: Removing duplicates with set() on a list of lists
- Cause 5: An in check against a set or dict
- Cause 6: Counter, dict.fromkeys and groupby-style counting
- Cause 7: A tuple that contains a list
- Cause 8: Passing a list to an @lru_cache or @cache function
- Cause 9: unhashable type: 'dict', 'set' and 'bytearray'
- Cause 10: A @dataclass instance in a set or as a key
- Python 3.14 changed the error message
- Why Python will not let you hash a list anyway
- Quick fix table for every cause
- How to find the list that caused it
- Frequently asked questions
What "unhashable type: 'list'" actually means
A hash is an integer that Python computes from a value. Dicts and sets use it like an address, so they can jump straight to the right slot instead of scanning every item. That only works if the hash of a key stays the same for as long as the key lives in the dict.
Here is a quick test of which common values are hashable. It calls hash() on each one and also checks isinstance(v, Hashable):
from collections.abc import Hashable
values = [42, "text", (1, 2), (1, [2]), [1, 2], {"a": 1}, {1}, frozenset({1})]
for v in values:
try:
hash(v)
ok = "hashable"
except TypeError as e:
ok = f"TypeError: {e}"
print(f"{v!r:>16} isinstance Hashable={isinstance(v, Hashable)!s:5} hash() -> {ok}")
42 isinstance Hashable=True hash() -> hashable
'text' isinstance Hashable=True hash() -> hashable
(1, 2) isinstance Hashable=True hash() -> hashable
(1, [2]) isinstance Hashable=True hash() -> TypeError: unhashable type: 'list'
[1, 2] isinstance Hashable=False hash() -> TypeError: unhashable type: 'list'
{'a': 1} isinstance Hashable=False hash() -> TypeError: unhashable type: 'dict'
{1} isinstance Hashable=False hash() -> TypeError: unhashable type: 'set'
frozenset({1}) isinstance Hashable=True hash() -> hashable
Numbers, strings, tuples and frozensets are hashable. Lists, dicts and sets are not. Look closely at the fourth line: (1, [2]) passes the isinstance check but still fails hash(), because a tuple is only hashable when everything inside it is hashable. That is why hash() inside a try block is the reliable test, as the collections.abc documentation also hints for Hashable.
Cause 1: Using a list as a dictionary key
This is the classic version. You want to store a price for a combination of values, and the combination happens to be a list:
prices = {}
item = ["apple", "red"]
prices[item] = 1.20
Traceback (most recent call last):
File "c01_dict_key.py", line 3, in <module>
prices[item] = 1.20
~~~~~~^^^^^^
TypeError: unhashable type: 'list'
The fix is to turn the list into a tuple. Tuples are immutable, so they make good keys, and you can still look the value up with a tuple literal later:
prices = {}
item = ["apple", "red"]
prices[tuple(item)] = 1.20
print(prices)
print(prices[("apple", "red")])
pairs = [[1, 2], [3, 4], [1, 2]]
print(set(map(tuple, pairs)))
# keep the original order while removing duplicates
print([list(t) for t in dict.fromkeys(map(tuple, pairs))])
{('apple', 'red'): 1.2}
1.2
{(1, 2), (3, 4)}
[[1, 2], [3, 4]]
The same file also shows the fix for cause 4 below: set(map(tuple, pairs)) removes duplicate pairs, and dict.fromkeys() does the same while keeping the original order. If you only need to remove duplicates from a flat list, my guide on removing duplicates from a Python list covers more options.
Cause 2: Putting lists inside a set literal
Curly braces with values and no colons create a set, and every set element must be hashable:
points = {[0, 0], [1, 2]}
Traceback (most recent call last):
File "c02_set_literal.py", line 1, in <module>
points = {[0, 0], [1, 2]}
^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'list'
Write the points as tuples instead: {(0, 0), (1, 2)}.
Cause 3: Calling set.add() with a list
A common pattern for tracking visited items is a seen set. It breaks as soon as the item is a list:
seen = set()
row = [3, 4]
seen.add(row)
Traceback (most recent call last):
File "c03_set_add.py", line 3, in <module>
seen.add(row)
~~~~~~~~^^^^^
TypeError: unhashable type: 'list'
Use seen.add(tuple(row)), and check membership the same way with tuple(row) in seen. Keep the conversion consistent, because a list and a tuple with the same contents are never equal to each other.
Cause 4: Removing duplicates with set() on a list of lists
set(my_list) is the fastest way to remove duplicates, until the list contains other lists:
pairs = [[1, 2], [3, 4], [1, 2]]
unique = set(pairs)
Traceback (most recent call last):
File "c04_dedupe.py", line 2, in <module>
unique = set(pairs)
TypeError: unhashable type: 'list'
Notice that this traceback has no caret line under the code. Python could not point at a single sub-expression, so read the last line carefully. The fix is the map(tuple, pairs) version from cause 1. When the inner order does not matter, so ["a", "b"] and ["b", "a"] count as the same group, use frozenset or a sorted tuple:
groups = [["b", "a"], ["a", "b"], ["c"]]
print({frozenset(g) for g in groups})
print({tuple(sorted(g)) for g in groups})
{frozenset({'b', 'a'}), frozenset({'c'})}
{('a', 'b'), ('c',)}
Cause 5: An in check against a set or dict
This one surprises people because no dict or set is being built. Membership tests on sets and dict keys hash the value on the left:
allowed = {"admin", "editor"}
user_roles = ["editor"]
print(user_roles in allowed)
Traceback (most recent call last):
File "c05_in_check.py", line 3, in <module>
print(user_roles in allowed)
^^^^^^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'list'
The code asks whether the whole list is one element of allowed, which is almost never what you mean. Usually you want to know whether any or all of the roles are allowed. Set operations answer that directly, and the set types reference lists all of them:
allowed = {"admin", "editor"}
user_roles = ["editor", "viewer"]
print(any(role in allowed for role in user_roles))
print(set(user_roles) & allowed)
print(set(user_roles) <= allowed)
True
{'editor'}
False
& gives the overlap, and <= asks whether every role is in allowed. It returned False here because "viewer" is not allowed.
Cause 6: Counter, dict.fromkeys and groupby-style counting
Anything that counts or groups values uses them as dict keys under the hood. collections.Counter is a dict subclass, so a list of lists fails inside the standard library:
from collections import Counter
orders = [["tea", "milk"], ["coffee"], ["tea", "milk"]]
print(Counter(orders))
Traceback (most recent call last):
File "c06_counter.py", line 3, in <module>
print(Counter(orders))
~~~~~~~^^^^^^^^
File "__init__.py", line 611, in __init__
self.update(iterable, **kwds)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
File "__init__.py", line 703, in update
_count_elements(self, iterable)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
TypeError: unhashable type: 'list'
The traceback ends inside collections/__init__.py, which makes it look like a library bug. It is not. Walk up to the first line that is your own file, and you will find the list you passed in. Convert each item first, and for dict rows use a JSON string with sorted keys as the key:
import json
from collections import Counter
orders = [["tea", "milk"], ["coffee"], ["tea", "milk"]]
print(Counter(map(tuple, orders)))
rows = [{"a": 1, "b": [1, 2]}, {"b": [1, 2], "a": 1}]
print({json.dumps(r, sort_keys=True) for r in rows})
Counter({('tea', 'milk'): 2, ('coffee',): 1})
{'{"a": 1, "b": [1, 2]}'}
Both dict rows collapsed into one set entry because sort_keys=True makes key order irrelevant. The collections documentation describes Counter as a dict subclass for counting hashable objects, which is the whole rule in one phrase.
Cause 7: A tuple that contains a list
Converting the outer value to a tuple is not enough when there is a list inside it:
key = ("user", [1, 2])
cache = {key: "value"}
Traceback (most recent call last):
File "c07_tuple_with_list.py", line 2, in <module>
cache = {key: "value"}
^^^^^^^^^^^^^^
TypeError: unhashable type: 'list'
Hashing a tuple hashes every item in it, so the inner list fails. For one level, write ("user", tuple(ids)). For deeply nested data, such as JSON you loaded from an API, a small recursive helper freezes everything at once:
def freeze(obj):
"""Turn nested lists, dicts and sets into hashable tuples/frozensets."""
if isinstance(obj, dict):
return tuple(sorted((k, freeze(v)) for k, v in obj.items()))
if isinstance(obj, (list, tuple)):
return tuple(freeze(x) for x in obj)
if isinstance(obj, set):
return frozenset(freeze(x) for x in obj)
return obj
record = {"id": 7, "tags": ["new", "sale"], "size": {"w": 2, "h": 3}}
key = freeze(record)
print(key)
print(hash(key) == hash(freeze({"size": {"h": 3, "w": 2}, "tags": ["new", "sale"], "id": 7})))
(('id', 7), ('size', (('h', 3), ('w', 2))), ('tags', ('new', 'sale')))
True
Sorting the dict items means two dicts with the same content but a different key order produce the same frozen key, which the True on the last line confirms. To look at the shape of a nested API response first, the free tools page has browser-based helpers.
Cause 8: Passing a list to an @lru_cache or @cache function
functools.lru_cache stores results in a dict keyed by the arguments, so every argument must be hashable:
from functools import lru_cache
@lru_cache(maxsize=None)
def total(numbers):
return sum(numbers)
print(total([1, 2, 3]))
Traceback (most recent call last):
File "c08_lru_cache.py", line 7, in <module>
print(total([1, 2, 3]))
~~~~~^^^^^^^^^^^
TypeError: unhashable type: 'list'
The functools documentation states that the positional and keyword arguments of a cached function must be hashable. A clean fix keeps the public function list-friendly and caches a private tuple version:
from functools import lru_cache
@lru_cache(maxsize=None)
def _total(numbers):
return sum(numbers)
def total(numbers):
return _total(tuple(numbers))
print(total([1, 2, 3]))
print(total([1, 2, 3]))
print(_total.cache_info())
6
6
CacheInfo(hits=1, misses=1, maxsize=None, currsize=1)
The second call was a cache hit, as cache_info() shows. The same rule applies to @functools.cache, which is lru_cache(maxsize=None) under another name.
Cause 9: unhashable type: 'dict', 'set' and 'bytearray'
The same error appears with other mutable built-ins. Only the type name in the quotes changes:
for bad in ({"id": 1}, {1, 2}, bytearray(b"ab")):
try:
{bad: True}
except TypeError as e:
print(type(bad).__name__, "->", e)
dict -> unhashable type: 'dict'
set -> unhashable type: 'set'
bytearray -> unhashable type: 'bytearray'
The fixes follow the same pattern. Use a frozenset instead of a set, bytes instead of a bytearray, and a sorted tuple of items or a JSON string for a dict. If you need a read-only dict that can itself be a key, my post on Python frozendict explains what is available and what is still a proposal.
Cause 10: A @dataclass instance in a set or as a key
This version does not mention lists at all, but it has the same root cause. A regular dataclass is mutable, and it defines __eq__, so Python sets its __hash__ to None:
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
print(Point.__hash__)
visited = {Point(1, 2)}
None
Traceback (most recent call last):
File "c10_dataclass.py", line 9, in <module>
visited = {Point(1, 2)}
^^^^^^^^^^^^^
TypeError: unhashable type: 'Point'
The first line of output, None, is the hash method that the decorator removed. The dataclasses documentation explains the rules: with eq=True and frozen=True, a __hash__ is generated for you. Freezing the class fixes it and also blocks accidental changes:
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
visited = {Point(1, 2), Point(1, 2), Point(3, 4)}
print(visited)
try:
Point(1, 2).x = 5
except Exception as e:
print(type(e).__name__, "->", e)
{Point(x=1, y=2), Point(x=3, y=4)}
FrozenInstanceError -> cannot assign to field 'x'
The duplicate Point(1, 2) collapsed into one set entry, and trying to change x raised FrozenInstanceError. Avoid unsafe_hash=True unless you are sure nobody will change the fields after the object goes into a set.
Python 3.14 changed the error message
If you are on Python 3.14, you may never see the exact text unhashable type: 'list' as the whole message. Here is cause 1 on three versions:
Python 3.10:
Traceback (most recent call last):
File "c01_dict_key.py", line 3, in <module>
prices[item] = 1.20
TypeError: unhashable type: 'list'
Python 3.12:
Traceback (most recent call last):
File "c01_dict_key.py", line 3, in <module>
prices[item] = 1.20
~~~~~~^^^^^^
TypeError: unhashable type: 'list'
Python 3.14.8:
Traceback (most recent call last):
File "c01_dict_key.py", line 3, in <module>
prices[item] = 1.20
~~~~~~^^^^^^
TypeError: cannot use 'list' as a dict key (unhashable type: 'list')
Python 3.14 now says what you were trying to do with the list. Set cases get their own wording too, while lru_cache still uses the short form:
-- c03_set_add.py --
TypeError: cannot use 'list' as a set element (unhashable type: 'list')
-- c04_dedupe.py --
TypeError: cannot use 'list' as a set element (unhashable type: 'list')
-- c08_lru_cache.py --
TypeError: unhashable type: 'list'
The What's New in Python 3.14 page lists this as an improved error message. Python 3.10 also lacks the ~~~^^^ markers, which arrived in 3.11. The cause and the fix are the same on every version.
Why Python will not let you hash a list anyway
You might wonder why Python does not just hash the current contents of the list. The next example shows what goes wrong when you do that yourself with a custom class:
class Box:
def __init__(self, items):
self.items = items
def __eq__(self, other):
return self.items == other.items
def __hash__(self):
return hash(tuple(self.items))
b = Box([1, 2])
s = {b}
b.items.append(3)
print(b in s)
print(len(s), Box([1, 2, 3]) in s)
False
1 False
After append(3), the object is still in the set, but the set cannot find it, because it looks in the slot for the new hash while the object sits in the slot for the old one. Even a fresh Box([1, 2, 3]) is not found. The data model reference for __hash__ warns about exactly this: mutable objects that compare by value should not be hashable.
Quick fix table for every cause
Use this as a checklist when the traceback points at a line and you are not sure which pattern you hit:
| Where the error comes from | Typical line | Fix |
|---|
| List as a dict key | prices[["apple", "red"]] = 1.2 | Use tuple(item) as the key |
| List inside a set literal | {[0, 0], [1, 2]} | Use tuples: {(0, 0), (1, 2)} |
| set.add() with a list | seen.add(row) | seen.add(tuple(row)) |
| set() on a list of lists | set(pairs) | set(map(tuple, pairs)) |
| in check against a set or dict | user_roles in allowed | any(r in allowed for r in user_roles) |
| Counter or dict.fromkeys on lists | Counter(orders) | Counter(map(tuple, orders)) |
| Tuple that contains a list | {("user", [1, 2]): v} | Convert the inner list too |
| @lru_cache / @cache argument | total([1, 2, 3]) | Pass a tuple, or wrap the function |
| dict, set or bytearray as a key | {{'id': 1}: True} | frozenset, sorted tuple of items or json.dumps |
| @dataclass instance in a set | {Point(1, 2)} | @dataclass(frozen=True) |
Picking between tuple and frozenset comes down to one question: does order or duplication matter? If yes, use a tuple. If not, use a frozenset. My comparison of list vs tuple vs set vs dict goes deeper on that choice.
How to find the list that caused it
When the line has several values, these steps find the culprit quickly:
- Read the last traceback line that points at your own file, not the standard library.
- Look at the caret markers on Python 3.11+. They underline the key or element being hashed.
- Print
type(x) for each candidate key. Data from json.load(), CSV splits and str.split() is always a list. - Wrap the suspicious value in
hash() on its own line to confirm.
If the problem is a missing key rather than an unhashable one, see my guide to KeyError in Python.
Frequently asked questions
How do I fix TypeError: unhashable type: 'list'?
Convert the list to a tuple before using it as a dict key or set element, for example d[tuple(items)] = value. Use frozenset when order does not matter, and convert nested lists too.
Why are lists unhashable in Python?
A list can change after it is created, so its hash would change too. Dicts and sets rely on hashes that never change, so Python makes lists unhashable on purpose.
Can a tuple be unhashable?
Yes. A tuple is hashable only if every item in it is hashable. hash((1, [2])) raises TypeError: unhashable type: 'list' because of the list inside.
What does 'cannot use list as a dict key' mean?
It is the Python 3.14 wording of the same error. Python 3.14 adds context, so you see cannot use 'list' as a dict key (unhashable type: 'list'). The fix is the same.
How do I remove duplicate lists from a list?
Use set(map(tuple, pairs)) if order does not matter, or [list(t) for t in dict.fromkeys(map(tuple, pairs))] to keep the original order. Both convert each inner list to a tuple first.
How do I use lru_cache with a list argument?
Convert the list to a tuple before calling the cached function. A small wrapper that calls the cached version with tuple(numbers) keeps the public function easy to use.
How do I make a dataclass hashable?
Use @dataclass(frozen=True). With eq=True and frozen=True, the decorator generates __hash__ for you, so instances can go into sets and be used as dict keys.
Last checked: 9 November 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.