Skip to content
Happy Programming Guide
Start learning
Python

Handling TypeError: Unhashable Type in Python

Why lists, dicts and sets cannot be dictionary keys, what hashable really means, and five practical fixes including frozenset and custom classes.

A close-up of a computer keyboard

TypeError: unhashable type: 'list' means you used something changeable where Python needed something fixed — as a dictionary key, or as a member of a set. Dictionaries and sets find items by hash, and a value that can change would break that lookup. The fix is to convert to an immutable equivalent, usually a tuple.

What hashable means#

Python
hash("hello")      # fine
hash(42)           # fine
hash((1, 2))       # fine
hash([1, 2])       # TypeError: unhashable type: 'list'

A dictionary stores each entry in a slot chosen by the key’s hash. If the key could change afterwards, its hash would change, the entry would be in the wrong slot, and the value would become unreachable. Python prevents that by refusing mutable keys outright.

Hashable Not hashable
str, int, float, bool, None list
tuple of hashables dict
frozenset set
bytes bytearray
most custom objects tuple containing a list

Where it appears#

Python
# 1. As a dictionary key
data = {}
data[["a", "b"]] = 1                    # TypeError

# 2. In a set
seen = set()
seen.add([1, 2])                        # TypeError

# 3. Deduplicating a list of lists
unique = set([[1, 2], [3, 4]])          # TypeError

# 4. Counting
from collections import Counter
Counter([[1], [2]])                     # TypeError

# 5. Grouping by a computed key
groups = {}
for row in rows:
    groups[[row["a"], row["b"]]] = row   # TypeError

Fix 1: use a tuple#

Python
data = {}
data[("a", "b")] = 1

seen = set()
seen.add((1, 2))

unique = set(tuple(item) for item in [[1, 2], [3, 4], [1, 2]])
print(unique)      # {(1, 2), (3, 4)}

For the grouping case:

Python
groups = {}
for row in rows:
    key = (row["country"], row["year"])
    groups.setdefault(key, []).append(row)

Tuples are the right answer whenever order matters and the number of elements is fixed.

Fix 2: use a frozenset when order does not matter#

Python
a = frozenset(["python", "web"])
b = frozenset(["web", "python"])

print(a == b)          # True - order is irrelevant
print({a: 1}[b])       # 1 - the same key

tags = {}
tags[frozenset({"beginner", "python"})] = "tutorial"

Compare with tuples, where ("a", "b") and ("b", "a") are different keys. Choose based on whether the ordering carries meaning.

Fix 3: a canonical string for nested data#

Python
import json

def key_for(obj):
    """A stable string key for arbitrary nested JSON-like data."""
    return json.dumps(obj, sort_keys=True, separators=(",", ":"))


cache = {}
cache[key_for({"b": 2, "a": [1, 2]})] = "result"

print(cache[key_for({"a": [1, 2], "b": 2})])   # same key, found

sort_keys=True is what makes it canonical — two dictionaries with the same contents in a different order produce the same string. Without it, this quietly fails to match.

Fix 4: recursive conversion#

Python
def make_hashable(obj):
    """Convert nested structures into hashable equivalents."""
    if isinstance(obj, (list, tuple)):
        return tuple(make_hashable(x) for x in obj)
    if isinstance(obj, dict):
        return tuple(sorted((k, make_hashable(v)) for k, v in obj.items()))
    if isinstance(obj, (set, frozenset)):
        return frozenset(make_hashable(x) for x in obj)
    return obj


key = make_hashable({"tags": ["a", "b"], "id": 1})
print(hash(key))     # works

Useful for caching function results keyed on complex arguments.

The tuple-containing-a-list trap#

Python
key = (1, [2, 3])
hash(key)            # TypeError: unhashable type: 'list'

A tuple is only hashable if everything inside it is. The error still names list, which is confusing when the thing you passed was a tuple. Convert recursively, as above.

Fix 5: make your own class hashable#

Python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)


p = Point(1, 2)
{p: "here"}          # TypeError: unhashable type: 'Point'

Custom classes are hashable by default — but defining __eq__ removes that, because two objects that compare equal must hash equally, and Python cannot guess how. Supply both:

Python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return isinstance(other, Point) and (self.x, self.y) == (other.x, other.y)

    def __hash__(self):
        return hash((self.x, self.y))

A frozen dataclass does both for you:

Python
from dataclasses import dataclass


@dataclass(frozen=True)
class Point:
    x: float
    y: float


points = {Point(1, 2): "origin-ish"}
print(Point(1, 2) in points)     # True

The frozen=True is what makes it immutable and therefore safely hashable. Without it, eq=True alone sets __hash__ to None.

The caching case#

Python
from functools import lru_cache


@lru_cache(maxsize=128)
def summarise(values):
    return sum(values) / len(values)


summarise([1, 2, 3])       # TypeError - arguments must be hashable
summarise((1, 2, 3))       # fine

lru_cache keys its cache on the arguments, so they must be hashable. Either pass tuples, or wrap the function:

Python
def summarise(values):
    return _summarise(tuple(values))


@lru_cache(maxsize=128)
def _summarise(values):
    return sum(values) / len(values)

Questions people ask#

Why can a tuple be a key but not a list?

A tuple cannot be changed after creation, so its hash is stable for its whole lifetime. A list can, so its hash would become wrong.

Are all tuples hashable?

Only if every element is. A tuple containing a list or a dictionary is not.

How do I use a dictionary as a key?

You cannot directly. Convert to a sorted tuple of items, a frozenset of items, or a canonical JSON string. Which is right depends on whether you need to recover the original.

Is a string always hashable?

Yes, and so are numbers, booleans, None, bytes and frozensets. All are immutable.

Where to go next#

Python dictionaries, and what hashing actually doesRead next

Keep reading

Python

Introduction to Python

What Python is, what people build with it, how it compares with other languages, and how to run your first program in…

4 min read

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *