Python
Operator Overloading
[]
Programming
Python Tips

How to override the operator in Python?

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

In Python, you do not override operators directly. You implement special methods, often called dunder methods, and Python maps operators to those methods. This is how custom classes can support +, ==, [], len(), and many other language features.

How Operator Overloading Works

Python defines a special method for each supported operation. A few common examples are:

  • '__add__ for a + b'
  • '__eq__ for a == b'
  • '__getitem__ for a[index]'
  • '__len__ for len(a)'
  • '__mul__ and __rmul__ for multiplication in both operand orders'

You should overload only operators that make semantic sense for the type. Good operator overloading makes code clearer. Bad operator overloading makes code mysterious.

Arithmetic Example With +

Here is a small vector class that supports addition.

python
1from dataclasses import dataclass
2
3@dataclass(frozen=True)
4class Vector2:
5    x: float
6    y: float
7
8    def __add__(self, other):
9        if not isinstance(other, Vector2):
10            return NotImplemented
11        return Vector2(self.x + other.x, self.y + other.y)
12
13
14v1 = Vector2(1.0, 2.0)
15v2 = Vector2(3.0, 4.0)
16print(v1 + v2)

The important detail is return NotImplemented for unsupported types. That allows Python to try the reflected method on the other operand or eventually raise a proper TypeError.

Support Both Operand Orders

If your type should work on the right side of an operator, implement the reflected version too. Multiplication is a common example.

python
1from dataclasses import dataclass
2
3@dataclass(frozen=True)
4class Vector2:
5    x: float
6    y: float
7
8    def __mul__(self, scalar):
9        if not isinstance(scalar, (int, float)):
10            return NotImplemented
11        return Vector2(self.x * scalar, self.y * scalar)
12
13    def __rmul__(self, scalar):
14        return self.__mul__(scalar)
15
16
17v = Vector2(2.0, 5.0)
18print(v * 3)
19print(3 * v)

Without __rmul__, 3 * v would fail even though v * 3 works.

Overload [] With __getitem__

The article tag suggests the indexing operator, so it is worth calling out separately. To support obj[index], implement __getitem__.

python
1class Pair:
2    def __init__(self, left, right):
3        self.left = left
4        self.right = right
5
6    def __getitem__(self, index):
7        if index == 0:
8            return self.left
9        if index == 1:
10            return self.right
11        raise IndexError("Pair index out of range")
12
13
14p = Pair("alpha", "beta")
15print(p[0])
16print(p[1])

This makes sense because Pair has a clear positional interpretation. If your object is not naturally indexable, do not force __getitem__ onto it.

Equality and Hashing Need Consistency

If you overload comparison, think about hashing and mutability. Equal objects should behave consistently in sets and dictionaries.

python
1from dataclasses import dataclass
2
3@dataclass(frozen=True)
4class Money:
5    cents: int
6    currency: str
7
8    def __add__(self, other):
9        if not isinstance(other, Money):
10            return NotImplemented
11        if self.currency != other.currency:
12            raise ValueError("cannot add different currencies")
13        return Money(self.cents + other.cents, self.currency)
14
15
16print(Money(1500, "USD") + Money(250, "USD"))

This example also shows that operator overloading should reflect domain rules. Adding different currencies blindly would be misleading.

In-Place Operators

Operators such as += can use in-place methods like __iadd__. Choose carefully whether the object should mutate or return a new value.

python
1class Counter:
2    def __init__(self, value=0):
3        self.value = value
4
5    def __iadd__(self, other):
6        if not isinstance(other, int):
7            return NotImplemented
8        self.value += other
9        return self
10
11
12c = Counter(5)
13c += 3
14print(c.value)

For immutable value objects, returning new instances is often the better design. For mutable counters or buffers, in-place behavior can be natural.

Common Pitfalls

A common mistake is returning None instead of NotImplemented for unsupported operands. Another is overloading operators that do not match user expectations, which makes an API clever but hard to trust.

It is also easy to forget reflected methods, so mixed operand order fails unexpectedly. Finally, if you overload equality on mutable objects, be careful about using them as dictionary keys.

Summary

  • Python overloads operators through special dunder methods.
  • Implement only the operators that make sense for your type.
  • Return NotImplemented for unsupported operand types.
  • Use reflected methods such as __rmul__ when operand order should not matter.
  • Keep equality, hashing, and mutability behavior consistent.

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.