datarekha
Python Medium Asked at GoogleAsked at MetaAsked at AmazonAsked at Stripe

What is the contract between `__eq__` and `__hash__` in Python, and what breaks when you define only one?

The short answer

Objects that compare equal must have identical hash values. Python enforces this expectation by setting `__hash__` to `None` whenever you define `__eq__` without `__hash__`, making the object unhashable and ineligible as a dict key or set member. To restore hashability you must define both.

How to think about it

What’s really being tested

Python hash tables (dict, set) do a two-step lookup: hash first, then equality. If two objects compare equal but have different hashes, the lookup fails to find them — the hash points to the wrong bucket. So the language enforces a contract: a == b must imply hash(a) == hash(b).

Python can’t verify that you’ve upheld this contract in your custom class. But it does enforce the simpler half: if you define __eq__, it assumes you’ve changed what “equal” means and it sets __hash__ = None unless you provide one too.

Defining both correctly

The go-to pattern is to delegate __hash__ to a tuple of the fields that define equality. Tuple hashing is well-distributed and order-sensitive — exactly what you want:

Inheriting from a hashable parent

If you subclass a class that already defines __hash__ and you add __eq__, Python still sets __hash__ = None. You must explicitly forward it:

class NamedPoint(Point):
    def __init__(self, x, y, label):
        super().__init__(x, y)
        self.label = label

    def __eq__(self, other):
        if not isinstance(other, NamedPoint):
            return NotImplemented
        return super().__eq__(other) and self.label == other.label

    __hash__ = Point.__hash__  # opt back in explicitly

Returning NotImplemented

When the comparison involves an incompatible type, return NotImplemented (not False). Python then tries the reflected operation on the other operand, enabling interoperability with third-party types.

The key insight

Hash before equality is an optimization: comparing hashes is a cheap integer comparison that rules out most mismatches before the potentially expensive __eq__ runs. The contract a == b → hash(a) == hash(b) is what makes that optimization safe.

Learn it properly Dunder Methods

Keep practising

All Python questions

Explore further

Skip to content