Skip to main content

Command Palette

Search for a command to run...

I Struggled with OOP in Java. Python Made It Click.

How seeing real applications help

Updated
8 min read
I Struggled with OOP in Java. Python Made It Click.
S
I'm a CS student figuring out AI one concept at a time — Python, PyTorch, LangChain, and whatever comes next. This blog is my learning made public. Future plans somewhere between building AI products and teaching them.

Let me be honest with you.

When I first encountered Object Oriented Programming in my 4th semester, it was taught in Java. I struggled through the whole course. Never understood the why, never understood the what — so the how never stuck. I 'by-hearted' it, showed up for evaluations, wrote something, got some marks, and moved on.

That's not learning. That's surviving a semester.

It was in my 5th semester, when I started learning Python properly and following structured courses, that OOP finally started making sense. The why became clear. And once the why is clear, everything else follows.

This blog is what I wish someone had told me before that 4th semester. Not a dry textbook explanation. Just an honest breakdown of what OOP actually is, why it exists, and how it makes your code cleaner and more powerful.


So what actually is OOP?

Here's my definition — not the textbook one:

OOP lets you build your own custom data types.

That's it at its core. You define how your data type stores data, and what operations you can perform on that data. You're not just using int, float, list — you're creating your own types that model real world things.

Python's built-in types are a great example of this already at work. A list stores data in a certain way and gives you methods like .append(), .pop(), .sort(). Someone designed that. OOP is you doing the same thing — designing your own types for your own problems.


Let's build something — Shapes

Say you're writing a program that works with geometric shapes. Without OOP, you'd be writing loose variables and functions everywhere:

# Without OOP — messy, doesn't scale
radius = 5
area = 3.14159 * radius ** 2
circumference = 2 * 3.14159 * radius

length = 4
breadth = 6
rect_area = length * breadth
rect_perimeter = 2 * (length + breadth)

Now imagine 10 shapes. Or 100. This gets out of hand fast.

With OOP, you define a Circle type once — how it stores data, what it can do — and then just create as many circles as you need:

import math

class Circle:
    def __init__(self, radius):   # __init__ runs automatically when you create an object
        self.radius = radius      # self refers to the specific object being created

    def area(self):
        return math.pi * self.radius ** 2

    def circumference(self):
        return 2 * math.pi * self.radius


class Rectangle:
    def __init__(self, length, breadth):
        self.length = length
        self.breadth = breadth

    def area(self):
        return self.length * self.breadth

    def perimeter(self):
        return 2 * (self.length + self.breadth)

Now using them is clean:

c = Circle(5)              # creates a Circle object with radius 5
print(c.area())            # 78.53
print(c.circumference())   # 31.41

r = Rectangle(4, 6)        # creates a Rectangle object
print(r.area())            # 24
print(r.perimeter())       # 20

Notice something — both Circle and Rectangle have an area() method. Same name, different behavior. That's already a hint at one of the pillars we'll cover shortly.


A more powerful example — Projectile Motion

Let's take this further with something from physics. A classic problem: a ball thrown at an angle. You want to know its range, maximum height, and time of flight.

Without OOP, every time you solve this you'd rewrite the same formulas with different numbers. With OOP, you define a Projectile once:

import math

class Projectile:
    def __init__(self, velocity, angle_degrees):
        self.velocity = velocity
        self.angle = math.radians(angle_degrees)  # converting degrees to radians for math functions
        self.g = 9.8                               # acceleration due to gravity (m/s²)

    def time_of_flight(self):
        # total time the projectile stays in the air
        # formula: t = 2 * v * sin(θ) / g
        return (2 * self.velocity * math.sin(self.angle)) / self.g

    def max_height(self):
        # highest point the projectile reaches
        # formula: h = v² * sin²(θ) / 2g
        return (self.velocity ** 2 * math.sin(self.angle) ** 2) / (2 * self.g)

    def range(self):
        # horizontal distance covered
        # formula: R = v² * sin(2θ) / g
        return (self.velocity ** 2 * math.sin(2 * self.angle)) / self.g

Now solving for any projectile is just two lines:

ball = Projectile(velocity=30, angle_degrees=45)
print(f"Time of flight : {ball.time_of_flight():.2f} s")   # :.2f means 2 decimal places
print(f"Max height     : {ball.max_height():.2f} m")
print(f"Range          : {ball.range():.2f} m")

rocket = Projectile(velocity=100, angle_degrees=60)         # same class, different values
print(f"Rocket range   : {rocket.range():.2f} m")

The formulas are written once. The data lives with the object. You just plug in different values and get answers. That's the power of bundling data and operations together.


The 4 Pillars of OOP

Now that you've seen OOP in action, the 4 pillars will make much more sense.

1. Encapsulation

Wrapping data and methods together into a class — that's encapsulation. But it goes further. You can also restrict direct access to certain attributes and instead provide controlled methods to access or modify them.

class StudentRecord:
    def __init__(self, name, marks):
        self.name = name
        self.__marks = marks    # double underscore makes it private — can't access directly from outside

    def update_marks(self, new_marks):
        if 0 <= new_marks <= 100:           # validation — only valid marks allowed
            self.__marks = new_marks
        else:
            print("Invalid marks. Must be between 0 and 100.")

    def get_marks(self):
        return self.__marks                 # controlled way to read the data

s = StudentRecord("Shashank", 85)
s.update_marks(92)
print(s.get_marks())        # 92
s.update_marks(150)         # Invalid marks. Must be between 0 and 100.
# print(s.__marks)          # Error — restricted, can't access directly

Why does this matter? Because now you control how the data is accessed and modified. Direct access has no such protection — anyone could set marks to 150 or -10 and your program wouldn't know.


2. Abstraction

This is, in my opinion, the fundamental motive behind OOP.

When you call ball.range(), you don't need to think about the formula inside. When you call list.sort(), you don't need to know which sorting algorithm Python uses internally. The complexity is hidden. Only what's relevant to the user is visible.

You use abstraction every day without realising it — every time you call a method without caring how it works inside, that's abstraction doing its job.

# You don't need to know how area() works internally
# You just know it exists and what it gives you
c = Circle(7)
print(c.area())     # just works

3. Inheritance

If two classes share mostly the same data and operations, why write it twice? Let one inherit from the other.

class Shape:
    def __init__(self, color, filled):
        self.color = color
        self.filled = filled        # True if the shape is filled, False if just outline

    def info(self):
        status = "filled" if self.filled else "not filled"
        print(f"Color: {self.color}, {status}")


class Circle(Shape):                # Circle inherits everything from Shape
    def __init__(self, color, filled, radius):
        super().__init__(color, filled)   # super() calls Shape's __init__ — no need to rewrite it
        self.radius = radius

    def area(self):
        return math.pi * self.radius ** 2


class Rectangle(Shape):             # Rectangle also inherits from Shape
    def __init__(self, color, filled, length, breadth):
        super().__init__(color, filled)
        self.length = length
        self.breadth = breadth

    def area(self):
        return self.length * self.breadth


c = Circle("red", True, 5)
c.info()            # Color: red, filled  ← inherited from Shape
print(c.area())     # 78.53              ← Circle's own method

r = Rectangle("blue", False, 4, 6)
r.info()            # Color: blue, not filled  ← same inherited method
print(r.area())     # 24

Circle and Rectangle both get info() for free from Shape. You only write what's new or different in each child class.


4. Polymorphism

Same method name, different behavior depending on the object. No conflicts, because each method is wrapped inside its own class.

shapes = [Circle("red", True, 5), Rectangle("blue", False, 4, 6)]

for shape in shapes:
    print(shape.area())     # Python automatically calls the right area() for each object

Circle.area() and Rectangle.area() have the same name but do completely different things. Python knows which one to call based on the object. That's polymorphism — many forms, one interface.


Why does any of this matter?

When you use pandas for data analysis, you load a CSV and get back a DataFrame — an object of a pandas class, with hundreds of methods wrapped around it. You don't think about how any of it works internally. You just call .groupby() or .fillna() and it works. That's abstraction doing its job.

And it's not just pandas. Right from data analysis libraries to deep learning frameworks like PyTorch, to LLM orchestration tools like LangChain and LangGraph — everything is objects. All of it is well designed classes.

OOP is not a concept you learn once for an exam. It's the foundation everything you'll use is actually built on.

While learning this in Java, I never really saw the applications — that could just be my experience. With Python, I got to actually see how the tools I was already using were built on the same concepts. That's what made the difference. That's what made it click.


I'm Shashank, a pre-final year CS student learning Python, data science, and AI. Writing about everything as I build and learn.