Object-Oriented Programming
Object-Oriented Programming (OOP) is a programming paradigm that organizes software design around objects — entities that bundle data (attributes) and behavior (methods) into self-contained units. It is the dominant paradigm in modern software engineering.
This guide covers classes and objects, the four pillars of OOP (encapsulation, inheritance, polymorphism, abstraction), SOLID design principles, composition vs aggregation, code walkthroughs, and a 10-question practice quiz.
Inheritance Chain: Object Creation & Method Resolution
No classes defined yet
1Introduction
Object-Oriented Programming (OOP) is a paradigm that models software as a collection of objects — self-contained units that encapsulate data and behavior. Rather than thinking of a program as a sequence of instructions, OOP encourages you to think in terms of real-world entities and their interactions.
OOP is central to Software Engineering (modular design, maintainability), Systems Design (modeling complex domains), Programming Languages (type hierarchies, dispatch mechanisms), and Design Patterns (reusable architectural solutions). Languages like Java, Python, C++, C#, and Swift are all built around OOP principles.
Nearly every large-scale software system — from web frameworks (Django, Spring) to game engines (Unity, Unreal) to mobile apps (Android, iOS) — is built using OOP. Understanding these principles is essential for working on professional codebases and passing technical interviews.

2Key Definitions
Essential terms for understanding object-oriented programming at the university level.
Class
A blueprint defining the attributes and methods that objects of that type will have
Object (Instance)
A concrete entity created from a class, with its own attribute values
Attribute (Field)
A variable belonging to an object that stores its state
Method
A function belonging to a class that defines behavior of its objects
Constructor
Special method called automatically to initialize a new object (__init__ in Python)
Destructor
Special method called when an object is destroyed to release resources (~ClassName in C++)
Encapsulation
Bundling data and methods together; restricting direct access to internal state
Inheritance
Child class inherits attributes and methods from a parent class (“is-a” relationship)
Polymorphism
One interface, many forms — different classes respond differently to the same method call
Abstraction
Hiding complexity; exposing only essential features via abstract classes/interfaces
Composition
Strong “has-a” relationship — part's lifecycle tied to the whole (Car has Engine)
Aggregation
Weak “has-a” relationship — part can exist independently (Department has Professors)
Access Modifier
Keywords controlling visibility: public, private, protected
Abstract Class
A class that cannot be instantiated; meant to be subclassed with abstract methods implemented
Interface
A contract specifying methods a class must implement (Java/C#; ABCs in Python)
Design Pattern
Reusable solution template for common software design problems (Singleton, Factory, Observer)
3Classes and Objects
At the heart of OOP are two intertwined concepts: classes (blueprints) and objects (instances). A class defines what an entity looks like and can do; an object is a concrete realization of that definition in memory.
Classes: The Blueprints
A class defines attributes (data members) that hold state and methods (member functions) that define behavior. Think of it as an architect's blueprint — it specifies rooms and doors but isn't a building itself.
Objects: The Instances
An object is created by instantiating a class. Each object has its own set of attribute values but shares the method definitions from its class. You can create many objects from a single class.

Constructors, Methods, and Destructors
Constructor
Automatically called when an object is created. Initializes attributes to a valid state.
__init__(self) in Python
Method
A function belonging to a class that defines actions the object can perform.
def accelerate(self)
Destructor
Called when an object is destroyed. Releases resources like file handles.
__del__(self) / ~Class()
4Encapsulation
Encapsulation bundles data and the methods that operate on it within a single unit (the class), and hides internal state from direct external access. Interaction with an object's data happens only through its public methods.
Think of a smartphone. You interact through the touchscreen and buttons (public methods). The internal circuits, battery, and processor (private data) are hidden behind the casing. You don't need to understand the internals to use the phone, and the manufacturer can change the internals without changing how you use it.
Access Modifiers
Public
Accessible from anywhere. Forms the object's external interface.
self.name
Protected
Accessible within the class and its subclasses.
self._name
Private
Accessible only within the class itself. Hidden from outside.
self.__name
Benefits of Encapsulation
- Data protection: Prevents unauthorized modification of internal state
- Modularity: Each object manages its own state independently
- Flexibility: Internal implementation can change without affecting external code
- Debugging: Errors are isolated within well-defined boundaries
5Inheritance
Inheritance allows a new class (child/subclass) to inherit attributes and methods from an existing class (parent/superclass). This establishes an “is-a” relationship and enables code reuse.

Types of Inheritance
Single Inheritance
A child class inherits from exactly one parent class. The simplest and most common form.
class Dog(Animal)
Multiple Inheritance
A child inherits from two or more parents. Supported in Python and C++, not Java.
class FlyingFish(Fish, Bird)
Multilevel Inheritance
A chain of inheritance: GuideDog extends Dog, which extends Animal.
Animal → Dog → GuideDog
Hierarchical Inheritance
Multiple child classes inherit from the same parent class.
Animal → Dog, Cat, Bird
Composition vs. Inheritance
Inheritance (“is-a”)
Child extends parent. Tighter coupling. Good for genuine type hierarchies.
A Dog is an Animal
Composition (“has-a”)
Object contains another object. Looser coupling. More flexible.
A Car has an Engine
6Polymorphism
Polymorphism (Greek: “many forms”) allows objects of different classes to be treated as objects of a common type. A single interface adapts its behavior depending on the actual object type.

Method Overriding vs. Overloading
Method Overriding
Subclass provides a specific implementation of a method already defined in the parent class. Same name, same parameters.
Dog.speak() overrides Animal.speak()
Method Overloading
Multiple methods with the same name but different parameters. Common in Java/C++. Python uses default args instead.
add(int, int) vs add(float, float)
Think of a “play” button on a remote control. Pressing it on a DVD player starts a movie. On a music player, it plays a song. On a game console, it launches a game. The button (interface) is the same, but the action (implementation) varies by device (object type).
7Abstraction
Abstraction focuses on exposing only the essential features of an object while hiding the complex implementation details. It answers “what does it do?” without requiring knowledge of “how does it work?”
Abstract Classes vs. Interfaces
Abstract Class
- Cannot be instantiated directly
- Can have both abstract and concrete methods
- Can have instance variables
- Single inheritance (Java) / multiple (Python/C++)
Interface
- Defines a pure contract (method signatures only)
- No implementation (except default methods in Java 8+)
- No instance variables (only constants)
- A class can implement multiple interfaces
Driving a car is abstraction in action. You interact with the steering wheel, accelerator, and brake (the abstract interface). You don't need to understand the combustion engine, transmission gears, or hydraulic brake system. The complexity is abstracted away behind a simple interface.
8SOLID Principles
SOLID is an acronym for five design principles promoted by Robert C. Martin that make software more understandable, flexible, and maintainable.

S — Single Responsibility Principle
A class should have only one reason to change. Each class handles one responsibility.
Example: A Report class generates data; a ReportPrinter class handles printing. Don't combine both.
O — Open/Closed Principle
Software entities should be open for extension but closed for modification.
Example: Add new Shape subclasses without modifying the core drawing logic.
L — Liskov Substitution Principle
Subtypes must be substitutable for their base types without altering program correctness.
Example: A function expecting Animal should work correctly with Dog, Cat, or any subtype.
I — Interface Segregation Principle
Clients should not be forced to depend on interfaces they don't use. Prefer small, specific interfaces.
Example: Split a monolithic Worker interface into IWorker, IEater, ISleeper. A Robot only implements IWorker.
D — Dependency Inversion Principle
Depend on abstractions, not concretions. High-level modules should not depend on low-level modules.
Example: PaymentProcessor depends on an IPaymentGateway interface, not directly on StripeApi.
9Code Walkthroughs
Class Definition and Object Creation
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
return f"Hi, I am {self.name}, age {self.age}"
# Create objects (instances)
p1 = Person("Alice", 25)
p2 = Person("Bob", 30)
print(p1.greet()) # Hi, I am Alice, age 25
print(p2.greet()) # Hi, I am Bob, age 30Line 1: class Person: defines the blueprint with a name.
Lines 2-4: __init__ is the constructor — called automatically when creating an object. self refers to the instance being created.
Lines 10-11: Two separate objects are created, each with their own attribute values.
Key insight: Classes define blueprints; objects are concrete instances with their own state.
Encapsulation with Access Control
class BankAccount:
def __init__(self, holder, balance=0):
self.holder = holder # public
self.__balance = balance # private (name mangling)
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def get_balance(self):
return self.__balance
acct = BankAccount("Alice", 1000)
acct.deposit(500)
print(acct.get_balance()) # 1500
# print(acct.__balance) # AttributeError!Line 4: __balance uses double underscore for name mangling — Python makes it harder to access directly.
Lines 6-8: deposit() is a public method that validates input before modifying private state.
Line 16: Direct access to __balance raises an error — data is protected.
Key insight: Encapsulation protects internal state and forces interaction through controlled methods.
Inheritance and Method Overriding
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement")
def eat(self):
print(f"{self.name} is eating.")
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # Call parent constructor
self.breed = breed
def speak(self): # Override parent method
print(f"{self.name} says Woof!")
class Cat(Animal):
def speak(self): # Override parent method
print(f"{self.name} says Meow!")
dog = Dog("Buddy", "Golden Retriever")
dog.eat() # Inherited from Animal
dog.speak() # Overridden in Dog: "Buddy says Woof!"Line 11: class Dog(Animal) — Dog inherits from Animal.
Line 13: super().__init__(name) calls the parent constructor to initialize inherited attributes.
Line 16: speak() is overridden — Dog provides its own implementation.
Line 24: eat() is inherited from Animal and used directly.
Key insight: Inheritance allows code reuse; method overriding lets subclasses customize behavior.
Polymorphism and Abstract Base Classes
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def perimeter(self):
return 2 * 3.14159 * self.radius
class Rectangle(Shape):
def __init__(self, w, h):
self.w, self.h = w, h
def area(self):
return self.w * self.h
def perimeter(self):
return 2 * (self.w + self.h)
# Polymorphism: same interface, different behavior
shapes = [Circle(5), Rectangle(4, 6)]
for s in shapes:
print(f"Area: {s.area()}, Perimeter: {s.perimeter()}")Lines 3-10: Shape(ABC) is abstract — cannot be instantiated. Forces subclasses to implement area() and perimeter().
Lines 33-35: Polymorphism in action — the loop treats all shapes uniformly through the common Shape interface.
Key insight: Abstract classes enforce a contract; polymorphism lets you write generic code that works with any subtype.
10Memory Aids
“Class = cookie cutter (the mold). Object = cookie (the actual thing you eat). One cutter makes many cookies.”
“A capsule (pill) wraps medicine inside a shell. Encapsulation wraps data inside a class, with methods as the controlled opening.”
“Children inherit traits from parents — eye color, height, behaviors. Child classes inherit attributes and methods from parent classes.”
“A USB port is polymorphic — plug in a mouse, keyboard, or flash drive. Same interface, different devices, different behaviors.”
“S-ingle job, O-pen door (but locked code), L-ike-for-like swaps, I-nterfaces stay slim, D-epend on blueprints not bricks.”
“Inheritance = 'I am a...' (Dog is an Animal). Composition = 'I have a...' (Car has an Engine). When in doubt, prefer 'has-a'.”
11Common Mistakes
Mistake: Creating deep inheritance hierarchies for code reuse
Fix: Favor composition over inheritance. Use “has-a” relationships for flexibility. Inheritance creates tight coupling; composition keeps components independent and swappable.
Mistake: class Dog(Animal): def __init__(self, breed): self.breed = breed
Fix: Always call super().__init__() in child constructors to initialize inherited attributes. Otherwise, parent attributes like name will not exist.
Mistake: A Square class inheriting from Rectangle that breaks when width and height are set independently
Fix: Ensure subclasses can be used anywhere the parent is expected without breaking behavior. If a Square cannot honor Rectangle's contract, use composition or a shared interface instead.
Mistake: A single class that handles user auth, database queries, email sending, and logging
Fix: Each class should have one responsibility. Split into UserAuthenticator, DatabaseService, EmailService, and Logger. This makes each class easier to test, maintain, and reuse.
Mistake: Making all attributes public and modifying them directly from outside
Fix: Use private attributes with getter/setter methods that include validation. This protects invariants — for example, a balance should never go negative without authorization.
Mistake: Thinking method overriding and overloading are the same concept
Fix: Overriding = same method signature in a subclass (runtime polymorphism). Overloading = same name but different parameters in the same class (compile-time polymorphism in Java/C++). Python doesn't support traditional overloading.
Mistake: def __init__(self, items=[]) — all instances share the same list!
Fix: Use def __init__(self, items=None): self.items = items or []. Default mutable arguments are created once and shared across all calls, causing subtle bugs.
Mistake: def greet(): return "Hello" inside a class — missing self
Fix: Instance methods must include self as the first parameter: def greet(self). Without it, Python cannot bind the method to the instance.
Frequently Asked Questions
- What is the difference between a class and an object?
- A class is a blueprint or template that defines attributes (data) and methods (behavior). An object is a concrete instance of a class — the actual entity created in memory with specific attribute values. You can create many objects from one class, each with its own state.
- What is the difference between encapsulation and abstraction?
- Encapsulation bundles data and methods together and restricts direct access to internal state using access modifiers (public, private, protected). Abstraction hides implementation complexity and exposes only essential features through abstract classes and interfaces. Encapsulation is about data protection; abstraction is about simplification.
- When should I use inheritance vs. composition?
- Use inheritance for "is-a" relationships (a Dog IS an Animal). Use composition for "has-a" relationships (a Car HAS an Engine). The general guideline is "favor composition over inheritance" because composition provides looser coupling, better flexibility, and avoids fragile base class problems.
- What is method overriding vs. method overloading?
- Method overriding: a subclass provides a specific implementation for a method already defined in its superclass (same name, same parameters). Method overloading: defining multiple methods with the same name but different parameter types or counts in the same class. Python uses default arguments and *args/**kwargs instead of traditional overloading.
- What are the SOLID principles?
- SOLID is an acronym for five design principles: Single Responsibility (one reason to change), Open/Closed (open for extension, closed for modification), Liskov Substitution (subtypes must be substitutable for base types), Interface Segregation (prefer specific interfaces over general ones), and Dependency Inversion (depend on abstractions, not concretions).
- What is polymorphism and why is it useful?
- Polymorphism ("many forms") allows objects of different classes to be treated through a common interface. A function that expects an Animal can work with a Dog, Cat, or any subtype. This eliminates long if-elif chains, makes code extensible (add new types without modifying existing code), and enables flexible, decoupled designs.
Practice Quiz
Test your understanding of object-oriented programming — select the correct answer for each question.
1.What is the primary role of a class in Object-Oriented Programming?
2.Which OOP principle involves bundling data and methods within a single unit and restricting direct access to some of the object's components?
3.If a SportsCar class extends a Car class, what kind of relationship does this represent?
4.Which concept allows objects of different classes to be treated as objects of a common type, often demonstrated through method overriding?
5.What is the purpose of an abstract method in an abstract class?
6.Which of the following best describes Composition?
7.According to the Single Responsibility Principle (SRP), a class should have:
8.What is a constructor primarily used for in OOP?
9.Which SOLID principle states that software entities should be open for extension but closed for modification?
10.What is the Liskov Substitution Principle (LSP)?
Study Tips
- Model real-world objects: Pick everyday items (cars, bank accounts, animals) and practice defining them as classes with attributes and methods.
- Draw class diagrams: Use UML class diagrams to visualize inheritance hierarchies, composition, and aggregation relationships before writing code.
- Practice the four pillars independently: Write small programs focused on each pillar — one for encapsulation, one for inheritance, one for polymorphism, one for abstraction.
- Refactor procedural code: Take a procedural program and refactor it into an OOP design. This builds intuition for when and how to use classes.
- Apply SOLID to existing projects: Review your past code and identify violations of SOLID principles. Refactor to fix them.