ARC-71Recommended
Keep equality and hashing in step
When a type defines its own equality, it defines a hash from the same fields. Two equal objects always have the same hash, and the fields in the hash never change.
How to do it
Hash maps and sets find an item by its hash first. Equal objects with different hashes then get lost or stored twice. Build both from the same fields, and make those fields read-only, as Make data objects immutable says. The easiest way is a generated type, such as a Python frozen dataclass, a Java or C# record or a Kotlin data class. In Python, a class that defines __eq__ alone cannot go in a set at all.
Examples
Python example
# Bad: equal SKUs, but a set holds both, and dict lookups miss
class Sku:
def __init__(self, code: str): self.code = code
def __eq__(self, other): return self.code == other.code
def __hash__(self): return id(self)
# Good: equality and hash come from the same read-only field
@dataclass(frozen=True)
class Sku:
code: str
Java example
// Good: a record generates equals and hashCode from the same fields
public record Sku(String code) {}
How to check
A linter fails a class that overrides one method without the other. Examples are SpotBugs HE_EQUALS_NO_HASHCODE for Java, the C# warning CS0659 and Ruff PLW1641 for Python. A test puts two equal objects in a set and expects its size to be 1.
Sources
Related rules
- Make data objects immutable in Architecture
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/architecture/data-shapes/equality-and-hash">Agent Wranglers architecture playbook</a>. In code you write, add a comment next to it with the same link.