Python
Equality
Object-Oriented Programming
Python Classes
Software Development

Elegant ways to support equivalence equality in Python classes

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Supporting equality in Python classes is mostly about deciding what "same value" means for your domain and then implementing that rule consistently. The elegant solution is usually the smallest one that gets __eq__, hashing, and type behavior correct without surprising people who use the class.

Core Sections

What == should mean for your class

By default, user-defined objects compare by identity, not by value. Two different instances are considered unequal even if their fields match.

python
1class Point:
2    def __init__(self, x, y):
3        self.x = x
4        self.y = y
5
6
7print(Point(1, 2) == Point(1, 2))  # False

If your class represents a value object, such as a point, money amount, or date range, overriding __eq__ is appropriate.

A correct manual __eq__

The important detail is returning NotImplemented when the other object is not of a compatible type. That lets Python try reflected comparison or fall back cleanly.

python
1class Point:
2    def __init__(self, x, y):
3        self.x = x
4        self.y = y
5
6    def __eq__(self, other):
7        if not isinstance(other, Point):
8            return NotImplemented
9        return self.x == other.x and self.y == other.y
10
11
12print(Point(1, 2) == Point(1, 2))  # True
13print(Point(1, 2) == "1,2")        # False

Do not return False immediately for unrelated types unless that is truly what you want. NotImplemented is the more correct protocol-level answer.

Keep __hash__ aligned with equality

If two objects compare equal, they must produce the same hash when used in sets or as dictionary keys. If you override __eq__ on a mutable class and do nothing else, Python may make the class unhashable to protect you from subtle bugs.

For immutable value objects, implement both:

python
1class Point:
2    def __init__(self, x, y):
3        self.x = x
4        self.y = y
5
6    def __eq__(self, other):
7        if not isinstance(other, Point):
8            return NotImplemented
9        return (self.x, self.y) == (other.x, other.y)
10
11    def __hash__(self):
12        return hash((self.x, self.y))

If the object is mutable, it is often safer to avoid hashing entirely.

dataclass is the cleanest option most of the time

For straightforward value objects, dataclasses.dataclass is usually the most elegant solution because it generates __eq__ for you and can also generate a safe hash policy.

python
1from dataclasses import dataclass
2
3@dataclass(frozen=True)
4class Point:
5    x: int
6    y: int
7
8
9print(Point(1, 2) == Point(1, 2))  # True
10print({Point(1, 2), Point(1, 2)})  # one item

frozen=True is a strong default for value objects because it makes the equality contract easier to trust.

Be explicit about subclass behavior. Sometimes isinstance(other, BaseClass) is correct. Sometimes equality should require the exact same type so that subclasses do not accidentally compare equal while carrying different semantics.

python
1class Shape:
2    def __init__(self, area):
3        self.area = area
4
5    def __eq__(self, other):
6        if type(self) is not type(other):
7            return NotImplemented
8        return self.area == other.area

This stricter check avoids odd cases where two different subclasses happen to share the same data shape but should not be interchangeable.

Keep equality small and predictable

A good equality implementation should:

  • compare only the fields that define logical identity
  • avoid side effects
  • avoid expensive external lookups
  • stay consistent over the lifetime of the object

If equality depends on database state, timestamps, or mutable caches, code becomes hard to reason about quickly.

Common Pitfalls

  • Returning False instead of NotImplemented for incompatible types and breaking Python's comparison protocol.
  • Defining __eq__ without thinking about __hash__, then getting confusing behavior in sets or dictionaries.
  • Using mutable fields in a hash calculation and making key lookups unstable.
  • Comparing too many incidental fields instead of only the values that define real equivalence.
  • Letting subclass instances compare equal to base-class instances when their semantics are not actually the same.

Summary

  • Value-based equality in Python starts with a correct __eq__ implementation.
  • Return NotImplemented for unsupported types so Python can handle comparisons properly.
  • Keep hashing consistent with equality, or leave mutable objects unhashable.
  • Prefer @dataclass(frozen=True) for simple immutable value objects.
  • Define equality in terms of domain meaning, not merely whatever fields happen to exist.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.