Fix: Python TypeError: unhashable type: 'list'
Quick Answer
Learn why Python raises TypeError unhashable type list, dict, or set and how to fix it when using dictionary keys, sets, groupby, dataclasses, and custom classes.
The Error
You run your Python script and hit this traceback:
TypeError: unhashable type: 'list'The full traceback usually looks like this:
Traceback (most recent call last):
File "app.py", line 5, in <module>
my_dict = {[1, 2, 3]: "value"}
TypeError: unhashable type: 'list'You might also see the same error with different types:
TypeError: unhashable type: 'dict'
TypeError: unhashable type: 'set'This error means you tried to use a mutable object (a list, dict, or set) in a place that requires a hashable object. Dictionary keys, set members, and anything used as a lookup key in Python must be hashable. Lists, dictionaries, and sets are mutable, so Python cannot compute a stable hash for them, and it raises this error.
The fix depends on what you are trying to do and which data structure triggered the error. Below are the most common scenarios and their solutions.
Why This Happens
Python uses hash values to store and look up dictionary keys and set members efficiently. A hash is a fixed-size integer computed from an object’s contents. For this system to work, the hash must remain constant for the lifetime of the object. If the object changes, its hash would change too, and Python would lose track of it in the internal hash table.
Mutable types like list, dict, and set can change their contents at any time. If Python allowed you to use a list as a dictionary key, you could modify that list later, and the dictionary would break. The key would be “lost” because its hash no longer matches where it was stored.
Immutable types like str, int, float, tuple (if all elements are also hashable), frozenset, and bool have a fixed hash and are safe to use as keys.
Here is a quick reference:
| Type | Hashable? | Immutable Equivalent |
|---|---|---|
list | No | tuple |
dict | No | frozenset of items or json.dumps |
set | No | frozenset |
tuple | Yes* | N/A |
str | Yes | N/A |
int | Yes | N/A |
*A tuple is hashable only if all of its elements are also hashable. A tuple containing a list is not hashable.
This is the same principle behind related Python errors. If you are also dealing with key-related issues, check out the guide on Python KeyError for when a key is missing entirely.
Fix 1: Convert a List to a Tuple for Dictionary Keys or Set Members
The most common trigger is using a list where a tuple is needed. Lists are mutable, but tuples are not. Convert the list to a tuple:
Problem:
my_dict = {}
key = [1, 2, 3]
my_dict[key] = "value" # TypeError: unhashable type: 'list'Fix:
my_dict = {}
key = [1, 2, 3]
my_dict[tuple(key)] = "value" # WorksThe same applies when adding to a set:
my_set = set()
my_set.add([1, 2, 3]) # TypeError
my_set.add(tuple([1, 2, 3])) # WorksIf your list contains nested lists, you need to convert recursively:
def deep_freeze(obj):
if isinstance(obj, list):
return tuple(deep_freeze(item) for item in obj)
if isinstance(obj, dict):
return tuple(sorted((k, deep_freeze(v)) for k, v in obj.items()))
if isinstance(obj, set):
return frozenset(deep_freeze(item) for item in obj)
return obj
nested = [[1, 2], [3, 4]]
key = deep_freeze(nested) # ((1, 2), (3, 4))Pro Tip: If you find yourself converting lists to tuples frequently, consider whether you should be using tuples from the start. If the data is not meant to change, define it as a tuple with parentheses
(1, 2, 3)instead of square brackets[1, 2, 3]. This avoids the conversion step entirely and makes your intent clearer.
Fix 2: Use frozenset Instead of set
If you need to use a set as a dictionary key or as a member of another set, use frozenset. A frozenset is the immutable version of a set:
Problem:
my_dict = {}
key = {1, 2, 3}
my_dict[key] = "value" # TypeError: unhashable type: 'set'Fix:
my_dict = {}
key = frozenset({1, 2, 3})
my_dict[key] = "value" # WorksThis is common when you want to group items by a combination of values where order does not matter:
# Group edges in a graph by unordered pairs
edges = [(1, 2), (2, 1), (3, 4), (4, 3)]
unique_edges = set()
for a, b in edges:
unique_edges.add(frozenset([a, b]))
print(unique_edges) # {frozenset({1, 2}), frozenset({3, 4})}frozenset supports the same operations as set (union, intersection, difference) but cannot be modified with add() or remove().
Fix 3: Fix Using Lists as Dictionary Keys
Sometimes you build a dictionary dynamically and accidentally use a list as a key. This is common when reading data from files or APIs where the structure is not obvious.
Problem:
data = {"name": "Alice", "scores": [90, 85, 92]}
# Trying to invert the dictionary
inverted = {v: k for k, v in data.items()}
# TypeError: unhashable type: 'list' (when v is [90, 85, 92])Fix — skip unhashable values:
inverted = {v: k for k, v in data.items() if isinstance(v, (str, int, float, bool, tuple))}Fix — convert lists to tuples:
inverted = {}
for k, v in data.items():
if isinstance(v, list):
inverted[tuple(v)] = k
else:
inverted[v] = kThis kind of type mismatch also shows up when handling data from APIs. If you are debugging a related None-value issue in your data pipeline, see Fix: Python TypeError: ‘NoneType’ object is not subscriptable.
Fix 4: Fix Using Lists in Sets
Sets require all members to be hashable. If you have a list of lists and want unique sublists, convert the inner lists to tuples first:
Problem:
data = [[1, 2], [3, 4], [1, 2]]
unique = set(data) # TypeError: unhashable type: 'list'Fix:
data = [[1, 2], [3, 4], [1, 2]]
unique = set(tuple(item) for item in data)
print(unique) # {(1, 2), (3, 4)}If you need the results back as lists:
unique_lists = [list(item) for item in unique]
print(unique_lists) # [[1, 2], [3, 4]]A common variation is deduplicating rows from a CSV or database query:
rows = [["Alice", 30], ["Bob", 25], ["Alice", 30]]
unique_rows = list(set(tuple(row) for row in rows))
# [('Alice', 30), ('Bob', 25)]Fix 5: Fix Dataclass Unhashable Error with frozen=True
Python dataclasses are not hashable by default. If you use a dataclass instance as a dictionary key or set member, you get the unhashable error:
Problem:
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
points = {Point(1, 2), Point(3, 4)} # TypeError: unhashable type: 'Point'Fix — use frozen=True:
from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: int
y: int
points = {Point(1, 2), Point(3, 4)} # Works
my_dict = {Point(1, 2): "origin"} # Also worksSetting frozen=True makes the dataclass immutable. You cannot change its fields after creation, which allows Python to compute a stable hash. If you try to modify a field, you get a FrozenInstanceError.
If you need mutability and hashing (rare, and risky), you can set unsafe_hash=True instead:
@dataclass(unsafe_hash=True)
class Point:
x: int
y: intWarning: Using unsafe_hash=True means you promise not to mutate the object while it is stored in a dict or set. If you do mutate it, the dict or set will silently break. Only use this when you fully control the object’s lifecycle.
This pattern is useful for attribute-related issues on objects. If you are working with classes and hitting attribute errors, see Fix: Python AttributeError: ‘NoneType’ has no attribute.
Fix 6: Fix Pandas Unhashable Type in groupby or merge
Pandas raises this error when a DataFrame column contains lists and you try to use it in groupby, merge, drop_duplicates, or value_counts:
Problem:
import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Alice"],
"tags": [["python", "java"], ["go"], ["python", "java"]]
})
df.groupby("tags").count()
# TypeError: unhashable type: 'list'Fix 1 — convert list column to tuples:
df["tags_tuple"] = df["tags"].apply(tuple)
df.groupby("tags_tuple").count()Fix 2 — convert list column to a string representation:
df["tags_str"] = df["tags"].apply(lambda x: ",".join(sorted(x)))
df.groupby("tags_str").count()Fix 3 — explode the list column and then group:
df_exploded = df.explode("tags")
df_exploded.groupby("tags")["name"].count()The explode approach is often the best choice because it lets you analyze individual tags rather than treating each unique combination as a separate group.
For drop_duplicates, convert the list column to a string first:
df["tags_str"] = df["tags"].apply(str)
df_deduped = df.drop_duplicates(subset=["name", "tags_str"])Pandas index-related errors are another common pain point. If you are hitting index issues, check Fix: Python IndexError: list index out of range.
Fix 7: Use json.dumps for Complex Structures as Keys
When you need to use a complex nested structure (dicts, lists of dicts, mixed types) as a dictionary key, json.dumps with sort_keys=True produces a consistent, hashable string:
Problem:
cache = {}
config = {"host": "localhost", "ports": [8080, 8081]}
cache[config] = "result" # TypeError: unhashable type: 'dict'Fix:
import json
cache = {}
config = {"host": "localhost", "ports": [8080, 8081]}
key = json.dumps(config, sort_keys=True)
cache[key] = "result" # Works — key is a stringWhy sort_keys=True matters: Without it, {"a": 1, "b": 2} and {"b": 2, "a": 1} would produce different strings even though they represent the same data. sort_keys=True ensures consistent key ordering.
This technique is especially useful for memoization and caching:
import json
from functools import lru_cache
def make_key(args):
return json.dumps(args, sort_keys=True)
cache = {}
def expensive_query(filters):
key = make_key(filters)
if key in cache:
return cache[key]
result = run_database_query(filters) # Slow operation
cache[key] = result
return resultCommon Mistake: Do not use
str()instead ofjson.dumps()for this purpose.str({"b": 2, "a": 1})may produce different strings across Python versions or runs because dict ordering instr()output depends on insertion order.json.dumps(sort_keys=True)is deterministic regardless of insertion order.
Note: json.dumps only works with JSON-serializable types (dicts, lists, strings, numbers, booleans, None). If your structure contains custom objects, datetime, or bytes, you need a custom serializer or the deep_freeze approach from Fix 1.
Fix 8: Fix Custom Class Hashing with __hash__ and __eq__
By default, custom classes are hashable (they use the object’s id as the hash). But if you define __eq__ without __hash__, Python sets __hash__ to None, making the class unhashable:
Problem:
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.name == other.name and self.age == other.age
users = {User("Alice", 30)} # TypeError: unhashable type: 'User'This happens because Python assumes that if two objects can be equal (via __eq__), their hashes must also be equal. Since the default id-based hash does not guarantee this, Python disables hashing entirely.
Fix — implement both __hash__ and __eq__:
class User:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
if not isinstance(other, User):
return NotImplemented
return self.name == other.name and self.age == other.age
def __hash__(self):
return hash((self.name, self.age))Now instances with the same name and age are considered equal and have the same hash:
user1 = User("Alice", 30)
user2 = User("Alice", 30)
print(user1 == user2) # True
print(hash(user1) == hash(user2)) # True
print({user1, user2}) # {User("Alice", 30)} — one itemRules for correct hashing:
- If
a == b, thenhash(a)must equalhash(b). - Use only immutable attributes in the hash calculation. If you hash based on a mutable attribute and then change it, the object becomes “lost” in the dict or set.
- Return
NotImplementedfrom__eq__when comparing with an incompatible type, so Python can try the other object’s__eq__.
If your class has many fields, use the fields relevant for equality:
class Config:
def __init__(self, host, port, debug):
self.host = host
self.port = port
self.debug = debug # Not relevant for equality
def __eq__(self, other):
if not isinstance(other, Config):
return NotImplemented
return self.host == other.host and self.port == other.port
def __hash__(self):
return hash((self.host, self.port))If you encounter argument-related TypeErrors while working with classes, see Fix: Python TypeError: missing required argument.
Still Not Working?
If the fixes above did not solve your issue, try these additional approaches:
Check for hidden lists in your data. Print the type of the object causing the error:
print(type(your_variable))
print(repr(your_variable))Sometimes a variable you expect to be a string or number is actually a list because of how it was parsed or returned from a function.
Check for nested mutable objects. A tuple containing a list is not hashable:
t = (1, [2, 3])
hash(t) # TypeError: unhashable type: 'list'You need to convert all nested mutable objects. Use the deep_freeze function from Fix 1.
Check for pandas Series being used as keys. If you accidentally pass a pandas Series where a scalar is expected:
# Wrong — passes a Series
df[df["col"]]
# Right — use .values or .tolist() and iterate
for val in df["col"].unique():
print(val)Check your Python version. In Python 3.6+, regular dicts maintain insertion order. But they are still mutable and not hashable. Do not confuse “ordered” with “immutable.”
Use a debugger to find the exact line. Run your script with the -m pdb flag or add a breakpoint:
breakpoint() # Drops into the debugger at this lineThen inspect the variables at the point of failure to see which one is the unexpected list, dict, or set.
If none of these solutions work, the issue is likely in a library you are using. Check the library’s documentation or GitHub issues for known problems with unhashable types in the specific method you are calling.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AWS Lambda Unable to import module / Runtime.ImportModuleError
How to fix the AWS Lambda Runtime.ImportModuleError and Unable to import module error caused by wrong handler paths, missing dependencies, layer issues, and packaging problems.
Fix: Django Forbidden (403) CSRF verification failed
How to fix Django 403 CSRF verification failed error caused by missing CSRF tokens, AJAX requests, cross-origin issues, HTTPS misconfig, and session problems.
Fix: FastAPI 422 Unprocessable Entity (validation error)
How to fix FastAPI 422 Unprocessable Entity error caused by wrong request body format, missing fields, type mismatches, query parameter errors, and Pydantic validation.
Fix: Python RuntimeError: no running event loop / This event loop is already running
How to fix Python asyncio RuntimeError no running event loop and event loop already running caused by mixing sync and async code, Jupyter, and wrong loop management.