In Python, everything is an object, and operators like +, -, *, etc., are implemented as special methods inside classes.
You can override these methods to define custom behavior for your own objects.
This concept is known as Operator Overloading, and it’s implemented using Dunder Methods (Double Underscore Methods).
Let’s break it down 👇
🔹 1. What is Operator Overloading?
Operator Overloading means redefining how built-in operators (
+,-,*,/,==, etc.) work for user-defined objects.
Normally, operators work with primitive data types:
print(10 + 5) # 15
print("Hello " + "World") # Hello WorldBut what if you want to use + between two custom objects?
That’s where operator overloading comes in.
🧩 Example Without Overloading
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p1 = Point(2, 3)
p2 = Point(4, 5)
print(p1 + p2) # ❌ Error: unsupported operand type(s) for +: 'Point' and 'Point'Python doesn’t know how to add two Point objects, so it throws a TypeError.
✅ Example With Operator Overloading
We can define how + should work using the dunder method __add__():
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
# Operator overloading for +
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __str__(self):
return f"({self.x}, {self.y})"
p1 = Point(2, 3)
p2 = Point(4, 5)
p3 = p1 + p2 # calls p1.__add__(p2)
print(p3)Output:
(6, 8)✅ You’ve defined how the + operator should behave for your custom class.
🔹 2. What are Dunder Methods?
Dunder methods (short for Double UNDerscore) are special predefined methods in Python,
which begin and end with two underscores — like__init__,__add__,__str__, etc.
These methods allow you to customize class behavior, including:
- Initialization (
__init__) - String representation (
__str__) - Arithmetic (
__add__,__sub__) - Comparison (
__eq__,__lt__) - Object destruction (
__del__)
They’re also called Magic Methods or Special Methods.
🧩 Example: Dunder Method __init__
class Student:
def __init__(self, name, age): # Constructor
self.name = name
self.age = age✅ __init__() is called automatically when an object is created.
🧩 Example: Dunder Method __str__
class Student:
def __init__(self, name):
self.name = name
def __str__(self):
return f"Student Name: {self.name}"
s = Student("Dheeraj")
print(s)Output:
Student Name: Dheeraj✅ __str__() controls what’s displayed when you print() an object.
🔹 3. Common Dunder Methods Used for Operator Overloading
| Operator | Dunder Method | Example Usage |
|---|---|---|
+ | __add__(self, other) | a + b |
- | __sub__(self, other) | a - b |
* | __mul__(self, other) | a * b |
/ | __truediv__(self, other) | a / b |
// | __floordiv__(self, other) | a // b |
% | __mod__(self, other) | a % b |
** | __pow__(self, other) | a ** b |
== | __eq__(self, other) | a == b |
!= | __ne__(self, other) | a != b |
< | __lt__(self, other) | a < b |
> | __gt__(self, other) | a > b |
<= | __le__(self, other) | a <= b |
>= | __ge__(self, other) | a >= b |
🧩 Example: Overloading Multiple Operators
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Point(self.x * scalar, self.y * scalar)
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __str__(self):
return f"({self.x}, {self.y})"
p1 = Point(2, 3)
p2 = Point(4, 5)
print("Add:", p1 + p2)
print("Sub:", p1 - p2)
print("Mul:", p1 * 3)
print("Equal?", p1 == p2)Output:
Add: (6, 8)
Sub: (-2, -2)
Mul: (6, 9)
Equal? False✅ You’ve customized how +, -, *, and == behave for your class.
🔹 4. Example: Overloading Comparison Operators
class Box:
def __init__(self, volume):
self.volume = volume
def __lt__(self, other):
return self.volume < other.volume
def __eq__(self, other):
return self.volume == other.volume
b1 = Box(10)
b2 = Box(20)
b3 = Box(10)
print(b1 < b2) # True
print(b1 == b3) # True✅ Comparison operators (<, ==, etc.) now work with your custom objects.
🔹 5. Example: Overloading __len__, __getitem__, __repr__
These dunder methods make custom objects behave like built-in types:
class MyList:
def __init__(self, data):
self.data = data
def __len__(self):
return len(self.data)
def __getitem__(self, index):
return self.data[index]
def __repr__(self):
return f"MyList({self.data})"
ml = MyList([10, 20, 30])
print(len(ml)) # Calls __len__()
print(ml[1]) # Calls __getitem__()
print(ml) # Calls __repr__()Output:
3
20
MyList([10, 20, 30])✅ Now your custom class acts like a built-in list.
🔹 6. Operator Overloading Behind the Scenes
When Python sees an operator, it internally calls the corresponding dunder method:
| Expression | Internally Calls |
|---|---|
a + b | a.__add__(b) |
a - b | a.__sub__(b) |
a * b | a.__mul__(b) |
a == b | a.__eq__(b) |
len(a) | a.__len__() |
str(a) | a.__str__() |
🔹 7. Why Use Operator Overloading?
✅ Makes custom classes behave like built-in types
✅ Makes code more readable and natural
✅ Useful in mathematical, scientific, and data modeling applications
🧩 Example: Without Operator Overloading
v1.add(v2)
v1.multiply(3)✅ With Operator Overloading
v1 + v2
v1 * 3✅ Cleaner and more intuitive!
🔹 8. Commonly Used Dunder Methods (Summary)
| Category | Dunder Method | Purpose |
|---|---|---|
| Initialization | __init__, __new__, __del__ | Object creation/destruction |
| Representation | __str__, __repr__ | String display |
| Arithmetic | __add__, __sub__, __mul__, etc. | Mathematical operations |
| Comparison | __eq__, __lt__, __gt__, etc. | Comparison operators |
| Container Behavior | __len__, __getitem__, __setitem__ | Like list/dict access |
| Context Manager | __enter__, __exit__ | Used with with statements |
| Callable Objects | __call__ | Make objects callable like functions |
✅ In short:
- Dunder methods are special methods surrounded by
__double_underscores__.- Operator overloading allows you to redefine how operators work for your custom classes.
- They make custom objects behave like built-in Python types, improving readability and flexibility.
🧩 Final Example (Quick Recap)
class ComplexNumber:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __add__(self, other):
return ComplexNumber(self.real + other.real, self.imag + other.imag)
def __str__(self):
return f"{self.real} + {self.imag}i"
c1 = ComplexNumber(3, 2)
c2 = ComplexNumber(1, 7)
print(c1 + c2)Output:
4 + 9i✅ + now behaves like mathematical addition for your custom class —
thanks to operator overloading and the dunder method __add__().
Perfect ✅ — let’s cover all 3 pattern types commonly asked in Python interviews:
