Immutable Keys & Hashable Objects

A data engineer was recently designing a system to track unique user sessions for an analytics platform. Their goal was to use a dictionary where each key represented a user's unique identifier, a combination of their user_id, device_type, and login_timestamp. Intuitively, they tried to use a Python list like [user_id, device_type, login_timestamp] as the dictionary key.

To their surprise, the code immediately threw a TypeError: unhashable type: 'list'. Why did this seemingly logical approach fail, and what does 'unhashable' even mean? This error isn't arbitrary; it points to a fundamental design principle behind Python's dictionaries and sets, a principle rooted in the concepts of immutability and hashability.

Encountering the 'Unhashable Type' Error

pythonAttempting to use a list as a dictionary key

The TypeError: unhashable type: 'list' is a direct consequence of how Python's dictionaries and sets are implemented internally. These data structures rely on a concept called hashing for their efficient operation. For an object to be used as a dictionary key or a set element, it must be hashable.

An object's hashability is intrinsically linked to its immutability. Understanding these two core concepts is crucial for writing robust and efficient Python code that leverages dictionaries and sets effectively.

Hashable
An object is hashable if it has a hash value that never changes during its lifetime (meaning it must be immutable) and can be compared to other objects. Hashable objects have a __hash__() method and an __eq__() method.
Example: Integers, strings, and tuples are hashable. For example, hash(10) returns a consistent integer value, and hash("hello") does the same.
Immutable
An object is immutable if its state cannot be modified after it is created. This means its value or contents cannot change once assigned. Immutability is a prerequisite for hashability because the hash value must remain constant.
Example: Numbers (integers, floats), strings, and tuples are immutable. Once you create my_string = "hello", you cannot change individual characters or append to it in place. Lists and dictionaries, however, are mutable.

Python's dictionaries and sets use a technique called hashing to quickly store and retrieve elements. When you add a key-value pair to a dictionary, Python first calculates a hash value for the key. This hash value is an integer that helps determine where in memory (often called a 'bucket') the key-value pair should be stored.

For this system to work reliably, the hash value of a key must never change. If a key's hash value could change after it's been added to a dictionary or set, Python would no longer be able to find it, leading to data loss or incorrect lookups. This is precisely why keys and set elements must be immutable: their internal state, and thus their hash value, remains constant throughout their existence.

The Hashing Process for Dictionaries and Sets
Loading diagram...
This flowchart illustrates how Python uses an object's hashability to determine its storage location for efficient lookup in dictionaries and sets. If an object is not hashable, a TypeError occurs.
Check Your Understanding
Why must dictionary keys and set elements be immutable?

Common Types: Hashable vs. Non-Hashable

Hashability of Common Python Built-in Types
TypeMutabilityHashable?Reason/Example
int, float, boolImmutableYesTheir value cannot change after creation.
strImmutableYesStrings cannot be modified in place.
tupleImmutableYesContents cannot be changed after creation (unless they contain mutable objects themselves).
frozensetImmutableYesAn immutable version of a set, specifically designed to be hashable.
listMutableNoElements can be added, removed, or changed.
dictMutableNoKey-value pairs can be added, removed, or changed.
setMutableNoElements can be added or removed.
This table summarizes the hashability of common Python types, directly linking it to their mutability.
pythonTesting Hashability and Using Valid Keys
Try It Yourself
Using the user_id, device_type, and login_timestamp from the initial scenario, create a dictionary user_activity where the keys are frozensets of the user details (e.g., frozenset({'user_abc', 'mobile', '2023-10-27T10:30:00Z'})) and the values are activity descriptions. Add at least two entries.
python
⚠️ The Mutable Trap in Tuples

While tuples are generally hashable, a tuple containing a mutable object (like a list or a dictionary) is not hashable. For example, (1, [2, 3]) cannot be used as a dictionary key. This is because the mutable element within the tuple could change, which would invalidate the tuple's hash value, violating the core principle of hashability.

Check Your Understanding
Which of the following Python objects would be suitable as a dictionary key?

Beyond Built-ins: Custom Hashable Objects (Briefly)

For more advanced scenarios, you might need to use instances of your own custom classes as dictionary keys or set elements. By default, user-defined classes are hashable if they don't implement __eq__ or if they implement __eq__ but also implement __hash__.

To make a custom object truly hashable and behave correctly, you typically need to implement both the __eq__ method (for equality comparison) and the __hash__ method (to provide a consistent hash value) in your class. This usually involves ensuring that all attributes used in __eq__ and __hash__ are themselves immutable. This is a more advanced topic, but it's good to know that Python provides the mechanisms to extend hashability to your own data types.

Key Takeaways
  • Dictionary keys and set elements must be hashable.

  • An object is hashable if its hash value never changes during its lifetime.

  • Immutability is a prerequisite for hashability; mutable objects cannot be hashable.

  • Common hashable types include int, float, str, tuple (if all its contents are immutable), and frozenset.

  • Common non-hashable types include list, dict, and set.

  • The TypeError: unhashable type indicates you're trying to use a mutable object where a hashable one is required.

  • To fix the initial scenario, the data engineer should use an immutable type like a tuple or frozenset for the user session key.

← All lessons in Data Structures: Dictionaries & Sets

Ready to keep this from fading?

Bitelrn turns lessons like this into a full course — quizzes, a knowledge map, and spaced review.

Get started free