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 World

But 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

OperatorDunder MethodExample 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:

ExpressionInternally Calls
a + ba.__add__(b)
a - ba.__sub__(b)
a * ba.__mul__(b)
a == ba.__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)

CategoryDunder MethodPurpose
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:


Scroll to Top