Hashable Objects in Python
What is a Hashable Object?
In Python, an object is hashable if:
- It has a hash value that remains constant during its lifetime (
__hash__()method). - It can be compared to other objects (
__eq__()method). - It is immutable (its state cannot change).
Hashable objects can be used as keys in dictionaries and elements in sets.
What Can Be Used as a Dictionary Key?
Only hashable objects can be used as keys in dictionaries.
Examples of hashable (valid key) types:
intfloatstrtuple(if all its elements are hashable)frozensetboolNoneType
my_dict = {
42: "an int",
"name": "a string",
(1, 2): "a tuple",
frozenset([1, 2, 3]): "a frozenset"
}
Examples of unhashable (invalid key) types:
listdictset
# This will raise a TypeError
my_dict = {
[1, 2, 3]: "a list", # unhashable
}
Tuple containing a list — also unhashable
A tuple is hashable only if all its elements are hashable. tuple.__hash__ recursively hashes each element; if any element raises TypeError on hash(), the whole tuple does too.
d = {(1, [2, 3]): "x"}
# TypeError: unhashable type: 'list'
hash((1, 2, 3)) # — all elements hashable
hash((1, (2, 3))) # — nested tuple, all hashable
hash((1, [2, 3])) # TypeError — list inside breaks it
hash((1, frozenset([2, 3]))) # — frozenset is hashable
The check happens at insertion time, not at definition time — so the error fires when you try to put the tuple into a dict or set.
Why mutability rules out hashability
If a key’s hash could change after insertion, the dict would lose track of it. The bucket is selected by hash at insert; a later hash change means lookup goes to the wrong bucket. The runtime can’t catch this — so it forbids hashable+mutable combinations entirely.
That’s why:
list(mutable) is unhashabletuple(immutable shell) is hashable, but only if elements are too — atuple([2, 3])would still be mutable through its elementfrozensetis hashable;setis not- Custom classes with
@dataclass(frozen=True)are hashable; default@dataclassis not (Python sets__hash__ = None)
See 23_eq_vs_hash.md for the full hash/eq contract and tricky_questions/34_mutating_dict_key_after_insertion.md for what goes wrong if you bypass it.
What Can Be Used as a Dictionary Value?
Any Python object can be used as a value in a dictionary — there are no restrictions.
my_dict = {
"username": "danil",
"score": 95,
"tags": ["python", "dev"],
"profile": {"age": 25, "country": "UA"},
"callback": lambda x: x * 2
}
Interview angle
- “What makes an object usable as a dict key?” - hashable: it implements
__hash__and its hash never changes while it’s in use. Mutable built-ins like list and dict are deliberately unhashable. - “What’s the
__eq__/__hash__contract?” - equal objects must have equal hashes. Defining__eq__without__hash__sets__hash__to None and makes the class unhashable, which is Python protecting you from a broken contract. - “What happens if a key mutates after insertion?” - its hash changes, so it lands in the wrong bucket and becomes unfindable - even by itself. This is why
frozen=Truedataclasses are the right choice for keys.