Skip to content

Fix: Python TypeError: unhashable type: 'list'

FixDevs ·

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:

TypeHashable?Immutable Equivalent
listNotuple
dictNofrozenset of items or json.dumps
setNofrozenset
tupleYes*N/A
strYesN/A
intYesN/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"  # Works

The same applies when adding to a set:

my_set = set()
my_set.add([1, 2, 3])        # TypeError
my_set.add(tuple([1, 2, 3])) # Works

If 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"  # Works

This 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] = k

This 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 works

Setting 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: int

Warning: 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 string

Why 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 result

Common Mistake: Do not use str() instead of json.dumps() for this purpose. str({"b": 2, "a": 1}) may produce different strings across Python versions or runs because dict ordering in str() 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 item

Rules for correct hashing:

  1. If a == b, then hash(a) must equal hash(b).
  2. 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.
  3. Return NotImplemented from __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 line

Then 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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles