Home › CS Fundamentals › OOP Concepts Explained: Object-Oriented Programming in Java and Python
Beginner 3 min · November 05, 2026

OOP Concepts Explained: Object-Oriented Programming in Java and Python

OOP concepts explained simply: classes, objects, encapsulation, inheritance, polymorphism and abstraction, with one analogy and runnable Java and Python.

N
Naren Founder & Principal Engineer

20+ years shipping production systems from the metal up. Everything here is grounded in real deployments.

Follow
✓ Production
production tested
November 06, 2026
last updated
2,128
articles · all by Naren
Before you start⏱ 15 min
  • ✓Basic variables and functions in any language
 ● Production Incident
⚡Quick Answer
  • OOP organises a program as objects: data plus the functions (methods) that act on it.
  • A class is the blueprint; an object is one thing built from it.
  • Encapsulation: an object guards its own data and exposes safe operations.
  • Inheritance: a new class reuses an existing one and changes only what differs.
  • Polymorphism: one call, such as cook(), behaves correctly for each kind of object.
  • Abstraction: callers depend on what an object can do, not how it does it.
✦ Definition~90s read
What is What Is Object-Oriented Programming??

Object-oriented programming (OOP) is a way of organising code around objects: bundles of data plus the actions that work on that data, each created from a blueprint called a class.

★
Think of a restaurant kitchen.
Plain-English First

Think of a restaurant kitchen. A recipe describes what a dish contains and how it's made; that's a class. Each plate that actually comes out of the kitchen is an object made from the recipe. The kitchen door keeps customers from wandering in and changing things (encapsulation). A pastry chef is still a chef, with the same basics plus a speciality (inheritance). When the head chef shouts "cook!", every chef makes their own dish (polymorphism). And the menu tells you what you can order without explaining how the kitchen works (abstraction).

Most beginners meet object-oriented programming as a list of four scary words. They're simpler than they sound. OOP is just a way to keep related data and behaviour together, so a large program stays understandable as it grows. This guide explains every idea with one running analogy, a restaurant kitchen, then shows each one in a few lines of Python, and finally all four together in Java. No prior OOP knowledge is assumed: if you can write a function and a variable, you can follow along.

OOP in One Sentence

Object-oriented programming means building a program out of objects, where each object holds some data and knows how to do things with it. Compare that with a purely procedural style, where data sits in variables and separate functions pass it around. Both work. OOP pays off when a program models many related things (users, orders, payments, chefs) and you want each one's rules to live in one place.

Almost every mainstream language supports OOP: Java and C# are built around it, Python, JavaScript, PHP and C++ support it alongside other styles.

Mental Model
The Kitchen Analogy
Recipe = class. Plate of food = object. Kitchen door = encapsulation. Pastry chef = inheritance. Shouting "cook!" = polymorphism. The menu = abstraction.
🎯 Key Takeaway
OOP groups data with the code that uses it, so each thing in your program manages itself.

Classes and Objects: The Recipe and the Dish

A class is a blueprint. It says what data every object of that kind has (its attributes) and what it can do (its methods). An object, also called an instance, is one concrete thing built from the class, with its own values.

Below, Dish is the recipe. pasta and salad are two separate objects made from it. They share the same structure and methods, but each keeps its own name and price.

classes.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Dish:
    """The recipe: what every dish has and can do."""

    def __init__(self, name, price):
        self.name = name        # data the object carries
        self.price = price

    def describe(self):         # behaviour the object can perform
        return f"{self.name} costs ${self.price:.2f}"


# Objects: real dishes made from the same recipe
pasta = Dish("Pasta", 12.5)
salad = Dish("Salad", 8)

print(pasta.describe())
print(salad.describe())
print(pasta is salad)           # two separate objects
Output
Pasta costs $12.50
Salad costs $8.00
False
💡What is self?
Inside a method, self is the specific object the method was called on. pasta.describe() runs describe with self set to pasta.
🎯 Key Takeaway
Write the class once; create as many objects from it as you need.

Encapsulation: The Kitchen Door

Encapsulation means an object protects its own data and only lets the outside world change it through methods that enforce the rules. Customers can order food, but they can't walk into the kitchen and change the recipe.

Here an Order keeps its list of items internal. The only way to add an item is add(), which rejects negative prices, and the total is read-only because it's always calculated from the items.

encapsulation.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Order:
    def __init__(self):
        self._items = []        # leading underscore: internal, don't touch

    def add(self, name, price):
        if price <= 0:
            raise ValueError("price must be positive")
        self._items.append((name, price))

    @property
    def total(self):            # read-only view, computed from the items
        return sum(price for _, price in self._items)


order = Order()
order.add("Pasta", 12.5)
order.add("Salad", 8)
print("Total:", order.total)

try:
    order.add("Free lunch", -5)
except ValueError as e:
    print("Rejected:", e)

try:
    order.total = 0             # no setter, so this is blocked
except AttributeError:
    print("Can't overwrite the total directly")
Output
Total: 20.5
Rejected: price must be positive
Can't overwrite the total directly
🔥Python vs Java
Python doesn't enforce privacy: a leading underscore is a convention meaning 'internal'. Java and C# enforce it with the private keyword.
🎯 Key Takeaway
Let the object guard its own data, so invalid states can't be created from outside.

Inheritance: A Pastry Chef Is Still a Chef

Inheritance lets a new class (the child or subclass) start from an existing one (the parent or base class). The child gets everything the parent has and only adds or changes what's different. A pastry chef greets people like any chef but cooks something different.

Use inheritance only for a true 'is a' relationship: a PastryChef is a Chef. If you're tempted to inherit just to reuse a few methods, composition (one object holding another) is usually the better choice.

inheritance.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Chef:
    def __init__(self, name):
        self.name = name

    def greet(self):
        return f"Hi, I'm {self.name}"

    def cook(self):
        return "cooks a main course"


class PastryChef(Chef):          # a PastryChef IS a Chef
    def cook(self):              # override just the part that differs
        return "bakes a tart"


anna = PastryChef("Anna")
print(anna.greet())              # inherited from Chef, not rewritten
print(anna.cook())               # its own version
print(isinstance(anna, Chef))
Output
Hi, I'm Anna
bakes a tart
True
⚠ Don't build deep family trees
Hierarchies more than two or three levels deep become hard to change. Prefer small hierarchies, and reach for composition when the 'is a' test fails.
🎯 Key Takeaway
Inheritance reuses a parent's behaviour and overrides only what differs.

Polymorphism: Everyone Responds to "Cook"

Polymorphism means 'many forms': the same method call does the right thing for each kind of object. The head chef shouts "cook!" once, and every chef makes their own dish. The code giving the order doesn't need to know which kind of chef it's talking to.

This is what removes long if/elif chains that check an object's type. Add a new kind of chef with its own cook() method and the loop below keeps working without a single change.

polymorphism.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Chef:
    def cook(self):
        return "main course"

class PastryChef(Chef):
    def cook(self):
        return "lemon tart"

class SushiChef(Chef):
    def cook(self):
        return "salmon nigiri"


kitchen = [Chef(), PastryChef(), SushiChef()]
for chef in kitchen:
    # Same call, different behaviour: the caller doesn't check types
    print(type(chef).__name__, "->", chef.cook())
Output
Chef -> main course
PastryChef -> lemon tart
SushiChef -> salmon nigiri
🎯 Key Takeaway
Polymorphism lets you add new kinds of objects without touching the code that uses them.

Abstraction: The Menu Hides the Kitchen

Abstraction means depending on what something can do, not how it does it. A menu lists dishes without explaining the cooking. In code, an abstract base class (or an interface in Java) defines the methods every implementation must provide, and the rest of the program works only with that contract.

Below, checkout() accepts any PaymentMethod. It doesn't know or care whether it's a card or cash. The abstract class itself can't be created, which forces every real payment type to implement pay().

abstraction.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from abc import ABC, abstractmethod


class PaymentMethod(ABC):
    """The menu: what any payment must offer, not how it works."""

    @abstractmethod
    def pay(self, amount): ...


class Card(PaymentMethod):
    def pay(self, amount):
        return f"Charged {amount} to card"


class Cash(PaymentMethod):
    def pay(self, amount):
        return f"Took {amount} in cash"


def checkout(method: PaymentMethod, amount):
    return method.pay(amount)    # works for any PaymentMethod


print(checkout(Card(), 20.5))
print(checkout(Cash(), 8))

try:
    PaymentMethod()
except TypeError:
    print("Can't create the abstract menu itself")
Output
Charged 20.5 to card
Took 8 in cash
Can't create the abstract menu itself
💡Abstraction vs encapsulation
Encapsulation hides an object's data. Abstraction hides how a whole category of objects works, behind a shared contract.
🎯 Key Takeaway
Program against the contract (what it does), so implementations can change freely.

All Four Pillars Together in Java

Java makes the same ideas explicit with keywords: interface for abstraction, private for encapsulation, extends for inheritance and @Override for polymorphism. This small kitchen uses all four in one file.

Kitchen.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.List;

public class Kitchen {

    // Abstraction: the contract every chef follows
    interface Cook {
        String cook();
    }

    // Encapsulation: name is private and can't be changed after creation
    static class Chef implements Cook {
        private final String name;

        Chef(String name) { this.name = name; }

        public String getName() { return name; }

        public String cook() { return "main course"; }
    }

    // Inheritance: a PastryChef is a Chef and overrides one behaviour
    static class PastryChef extends Chef {
        PastryChef(String name) { super(name); }

        @Override
        public String cook() { return "lemon tart"; }
    }

    public static void main(String[] args) {
        List<Chef> chefs = List.of(new Chef("Ravi"), new PastryChef("Anna"));
        for (Chef c : chefs) {
            // Polymorphism: the same call runs the right version
            System.out.println(c.getName() + " makes " + c.cook());
        }
    }
}
Output
Ravi makes main course
Anna makes lemon tart
🔥Static nested classes
The classes are nested inside Kitchen only to keep the example in one file. In a real project each would usually live in its own file.
🎯 Key Takeaway
The four pillars are the same in every OOP language; only the keywords change.

When OOP Is the Wrong Tool

OOP isn't always the best fit. A short script that reads a file and prints a report is clearer as a few functions. Data transformations, such as cleaning a list of records, are often simpler in a functional style with plain functions and immutable data. Overusing OOP shows up as classes with one method, getters and setters for everything, and deep inheritance trees that make a small change ripple everywhere.

A good rule: introduce a class when you have data and behaviour that genuinely belong together, or several kinds of thing that share a contract. Otherwise, a function is fine.

⚠ Signs you've over-engineered it
A class named Manager or Helper with no data, a hierarchy built 'for the future', or an interface with exactly one implementation that will never get a second.
🎯 Key Takeaway
Use OOP to manage complexity, not to add it.
● Production incidentPOST-MORTEMseverity: high

The New Customer Type Nobody's Code Knew About

Symptom
After a 'student' customer type was added, students were charged full price. No error was raised; the bug was found by a customer complaint.
Assumption
The team assumed updating the signup form and the database was enough to add a customer type.
Root cause
The discount logic lived in three different functions, each with its own if/elif chain on customer_type. Only one of them was updated; the others fell through to the default 'no discount' branch.
Fix
Each customer type became a class with its own discount() method, all sharing a common base class. Checkout now calls customer.discount() and never checks the type, so a new type only needs one new class.
Key lesson
  • Repeated if/elif chains on a 'type' field are a sign the code wants polymorphism.
  • Keep behaviour next to the data it belongs to, so there's one place to change.
  • A base class with a required method turns a forgotten case into an immediate error instead of a silent wrong answer.
PillarIn plain wordsKitchen analogyTypical keyword
EncapsulationAn object guards its own dataThe kitchen doorprivate (Java), _name (Python)
InheritanceReuse a class and change only what differsA pastry chef is still a chefextends (Java), class Child(Parent)
PolymorphismOne call, the right behaviour for each typeEveryone responds to "cook!"@Override, method overriding
AbstractionDepend on what it does, not howThe menu hides the kitcheninterface, abstract class, ABC
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
classes.pyclass Dish:Classes and Objects
encapsulation.pyclass Order:Encapsulation
inheritance.pyclass Chef:Inheritance
polymorphism.pyclass Chef:Polymorphism
abstraction.pyfrom abc import ABC, abstractmethodAbstraction
Kitchen.javapublic class Kitchen {All Four Pillars Together in Java

Key takeaways

1
OOP organises code into objects that combine data and behaviour.
2
A class is a blueprint; objects are the things built from it.
3
Encapsulation, inheritance, polymorphism and abstraction are the four core ideas.
4
Polymorphism replaces type-checking if/elif chains.
5
Prefer composition when inheritance's 'is a' test fails.
6
Use OOP where it reduces complexity; plain functions are fine for simple scripts.

Common mistakes to avoid

4 patterns
×

Thinking OOP means 'put everything in a class'

Symptom
Classes that hold no data and just wrap one function, making simple code harder to follow.
Fix
Use a plain function when there's no state to manage. Add a class when data and behaviour belong together.
×

Using inheritance just to reuse code

Symptom
A subclass that inherits methods it shouldn't have, such as a Stack extending a List and exposing insert-in-the-middle.
Fix
Apply the 'is a' test. If it fails, hold the other object as a field (composition) instead.
×

Checking types with if/elif instead of using polymorphism

Symptom
Every new type means hunting down and editing several if/elif chains, and missing one causes silent bugs.
Fix
Give each type its own method and call it; let the object decide what to do.
×

Confusing a class with an object

Symptom
Changing a value on one object and expecting every object to change, or the reverse with class-level attributes.
Fix
Remember: the class is the blueprint, each object has its own copy of instance attributes.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What are the four pillars of OOP? Explain each in one sentence.
Q02JUNIOR
What is the difference between a class and an object?
Q03SENIOR
When would you choose composition over inheritance?
Q01 of 03JUNIOR

What are the four pillars of OOP? Explain each in one sentence.

ANSWER
Encapsulation: an object hides its data and exposes controlled operations. Inheritance: a class reuses another class's behaviour and overrides what differs. Polymorphism: the same call behaves correctly for different object types. Abstraction: code depends on a contract (what an object does) rather than its implementation.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
What is object-oriented programming in simple terms?
02
Is Python an object-oriented language?
03
Is OOP hard to learn?
04
What is the opposite of object-oriented programming?
N
Naren Founder & Principal Engineer

20+ years shipping production systems from the metal up. Everything here is grounded in real deployments.

Follow
✓ Verified
production tested
November 06, 2026
last updated
2,128
articles · all by Naren
🔥

That's Software Engineering. Mark it forged?

3 min read · try the examples if you haven't

←
Previous
Agile and Scrum Explained
3 / 17 · Software Engineering
Next
SOLID Principles
→