Bhagavad Gita · 2.47
कर्मण्येवाधिकारस्ते मा फलेषु कदाचन
“You have the right to perform your duty, but not to the fruits of your actions.”
Bhagavad Gita · 2.47
कर्मण्येवाधिकारस्ते मा फलेषु कदाचन
“You have the right to perform your duty, but not to the fruits of your actions.”
We all learn about Object-Oriented Programming (OOP), classes, objects, and those four famous pillars: encapsulation, inheritance, polymorphism, and abstraction. Honestly, I never understood why we even learn this, but it turns out it does have some real-world use cases, and that’s exactly what we’re going to explore.
In this blog, we’re going to build one of the most popular applications, a To-Do app, using OOP concepts in Python.

Before we dive in, there are a few jargons you should be familiar with:
Almost every interviewer or viva examiner loves this question: “Explain the four pillars of OOP.”
They are encapsulation, inheritance, polymorphism, and abstraction.
But what exactly do these fancy terms mean in simple language?

Think of encapsulation as good housekeeping for your code. In our To-Do app, we’ll create classes that keep related data and methods bundled together while hiding the messy details from the outside world.
For example, our TodoItem class takes care of everything related to a single task:
class TodoItem:
def __init__(self, title):
self.__title = title
self.__completed = False
def mark_complete(self):
self.__completed = True
def get_status(self):
return "Completed" if self.__completed else "Pending"The beauty here? Other parts of the app don’t need to know how the task status is stored or changed.
They just call mark_complete() and get the job done.
The internal logic stays hidden and protected inside the class.
Note: __init__ is called automatically every time you create a new object from a class.
It’s known as the constructor, the method that initializes the object with its starting values.
Tasks in our To-Do app can come in different types – work tasks, personal tasks, or recurring ones.
Rather than rewriting the same logic for each, we can create a base class and let others build on it.

class Task:
def __init__(self, title):
self.title = title
self.completed = False
def mark_complete(self):
self.completed = True
class WorkTask(Task):
def __init__(self, title, deadline):
super().__init__(title)
self.deadline = deadline
Here, WorkTask inherits all the features of Task and adds a deadline of its own.
If we ever want to add a new common method (like saving to a file), we can do it once in the parent class, and all child classes will get it automatically.
Note: self simply refers to the current object — the specific instance of the class that’s being worked on

Polymorphism sounds fancy, but it’s simple: the same function name can have different behaviors depending on the object using it.
In our To-Do app, different types of tasks might have different ways of showing details.
class Task:
def show(self):
print(f"Task: {self.title}")
class WorkTask(Task):
def show(self):
print(f"Work Task: {self.title} (Deadline: {self.deadline})")
class PersonalTask(Task):
def show(self):
print(f"Personal Task: {self.title}")
Now, when we call show() on a list of mixed tasks, each one behaves appropriately without us writing multiple function names:
tasks = [WorkTask("Finish report", "2025-11-10"), PersonalTask("Buy groceries")]
for t in tasks:
t.show()
Users don’t need to know how the entire task system works — they just want to add, complete, or view tasks.
Abstraction helps us hide the complexity behind simple, clean interfaces.
from abc import ABC, abstractmethod
class TaskManager(ABC):
@abstractmethod
def add_task(self, title):
pass
@abstractmethod
def complete_task(self, title):
pass
class SimpleTaskManager(TaskManager):
def __init__(self):
self.tasks = []
def add_task(self, title):
self.tasks.append(Task(title))
def complete_task(self, title):
for t in self.tasks:
if t.title == title:
t.mark_complete()Here, TaskManager defines what actions a manager should support,
and SimpleTaskManager decides how those actions actually work internally.
The outside world just calls add_task() or complete_task() without caring about the internal logic.
Here’s what makes OOP so powerful, these four pillars work together to keep your project clean and scalable:
show() work polymorphically for all task typesThat’s how OOP turns a simple To-Do app into a maintainable, organized system you can easily expand over time.
