Classes and Objects in OOP Explained
TL;DR
Classes are blueprints that define the structure and behavior of objects. Objects are instances created from classes that hold actual data. Understanding classes and objects is fundamental to OOP — they let you model real-world entities and organize code into reusable, maintainable units.
Core Concept
What Are Classes?
A class is a template or blueprint that defines the structure and behavior of objects. Think of it like a cookie cutter — it defines the shape, but it’s not the cookie itself. Classes contain two main components:
- Attributes (also called properties or fields): Variables that store data about the object
- Methods: Functions that define what the object can do
What Are Objects?
An object (also called an instance) is a concrete realization of a class. When you create an object from a class, you’re instantiating that class. Each object has its own copy of the class’s attributes but shares the methods defined in the class.
For example, if Car is a class, then my_toyota and your_honda are objects. Both are cars, but they have different colors, models, and mileage.
The Constructor
A constructor is a special method that runs automatically when you create a new object. In Python, the constructor is the __init__ method. It initializes the object’s attributes with starting values.
Constructors solve a critical problem: ensuring every object starts in a valid state. Without constructors, you’d need to manually set every attribute after creating an object, which is error-prone.
Why Classes and Objects Matter
- Encapsulation: Group related data and functions together
- Reusability: Write the class once, create many objects
- Modeling: Represent real-world entities in code
- Maintainability: Changes to the class affect all objects automatically
In interviews, you’ll often be asked to design a class to model a system (parking lot, library, social network). Understanding the class-object relationship is the first step toward system design.
Visual Guide
Class vs Object Relationship
graph TD
A[Class: Car] -->|instantiate| B[Object: my_toyota]
A -->|instantiate| C[Object: your_honda]
A -->|instantiate| D[Object: her_tesla]
B -->|has| B1[color: red]
B -->|has| B2[model: Camry]
B -->|has| B3[year: 2020]
C -->|has| C1[color: blue]
C -->|has| C2[model: Accord]
C -->|has| C3[year: 2019]
style A fill:#e1f5ff
style B fill:#fff4e1
style C fill:#fff4e1
style D fill:#fff4e1
One class serves as a blueprint for multiple objects. Each object has its own attribute values but shares the same structure and methods.
Class Structure
classDiagram
class BankAccount {
-account_number: str
-balance: float
-owner: str
+__init__(account_number, owner, initial_balance)
+deposit(amount)
+withdraw(amount)
+get_balance()
}
note for BankAccount "Attributes store state\nMethods define behavior\nConstructor initializes objects"
A class contains attributes (data) and methods (behavior). The constructor (init) sets up initial state when objects are created.
Examples
Example 1: Basic Class with Constructor
Let’s create a Dog class to understand the fundamentals:
class Dog:
def __init__(self, name, age):
# Constructor: runs when we create a new Dog object
self.name = name # Instance attribute
self.age = age # Instance attribute
def bark(self):
# Instance method
return f"{self.name} says Woof!"
def get_info(self):
return f"{self.name} is {self.age} years old"
# Creating objects (instantiation)
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)
# Each object has its own attributes
print(dog1.name) # Output: Buddy
print(dog2.name) # Output: Max
# Calling methods
print(dog1.bark()) # Output: Buddy says Woof!
print(dog2.get_info()) # Output: Max is 5 years old
Key Points:
selfrefers to the specific object being created or used__init__is called automatically when you writeDog("Buddy", 3)- Each object (
dog1,dog2) has its ownnameandage - Methods are shared — both dogs use the same
barkmethod
Java Equivalent:
public class Dog {
private String name;
private int age;
// Constructor (same name as class, no __init__)
public Dog(String name, int age) {
this.name = name; // 'this' is like Python's 'self'
this.age = age;
}
public String bark() {
return name + " says Woof!";
}
}
Try it yourself: Add a birthday() method that increases the dog’s age by 1.
Example 2: Bank Account with Validation
Let’s build a more realistic class with input validation:
class BankAccount:
def __init__(self, account_number, owner, initial_balance=0):
self.account_number = account_number
self.owner = owner
self.balance = initial_balance # Default parameter
def deposit(self, amount):
if amount <= 0:
return "Deposit amount must be positive"
self.balance += amount
return f"Deposited ${amount}. New balance: ${self.balance}"
def withdraw(self, amount):
if amount <= 0:
return "Withdrawal amount must be positive"
if amount > self.balance:
return "Insufficient funds"
self.balance -= amount
return f"Withdrew ${amount}. New balance: ${self.balance}"
def get_balance(self):
return f"Account {self.account_number}: ${self.balance}"
# Creating accounts
account1 = BankAccount("12345", "Alice", 1000)
account2 = BankAccount("67890", "Bob") # Uses default balance of 0
print(account1.get_balance()) # Output: Account 12345: $1000
print(account2.get_balance()) # Output: Account 67890: $0
print(account1.deposit(500)) # Output: Deposited $500. New balance: $1500
print(account1.withdraw(200)) # Output: Withdrew $200. New balance: $1300
print(account1.withdraw(2000)) # Output: Insufficient funds
Key Points:
- Default parameters (
initial_balance=0) make constructors flexible - Methods can modify object state (
self.balance += amount) - Validation logic keeps objects in valid states
- Each account object maintains its own balance independently
Try it yourself: Add a transfer(amount, other_account) method that moves money from one account to another.
Example 3: Multiple Objects Interacting
class Student:
def __init__(self, name, student_id):
self.name = name
self.student_id = student_id
self.courses = [] # Empty list to store enrolled courses
def enroll(self, course_name):
self.courses.append(course_name)
return f"{self.name} enrolled in {course_name}"
def get_courses(self):
if not self.courses:
return f"{self.name} is not enrolled in any courses"
return f"{self.name}'s courses: {', '.join(self.courses)}"
# Creating multiple student objects
student1 = Student("Emma", "S001")
student2 = Student("Liam", "S002")
student3 = Student("Olivia", "S003")
# Each student enrolls in different courses
print(student1.enroll("Math 101")) # Output: Emma enrolled in Math 101
print(student1.enroll("Physics 201")) # Output: Emma enrolled in Physics 201
print(student2.enroll("Chemistry 101"))# Output: Liam enrolled in Chemistry 101
print(student1.get_courses()) # Output: Emma's courses: Math 101, Physics 201
print(student2.get_courses()) # Output: Liam's courses: Chemistry 101
print(student3.get_courses()) # Output: Olivia is not enrolled in any courses
Key Points:
- Attributes can be collections (lists, dictionaries)
- Each object’s list is independent — modifying
student1.coursesdoesn’t affectstudent2.courses - Objects can start with empty collections and build them up over time
Try it yourself: Add a drop_course(course_name) method that removes a course from the student’s list.
Common Mistakes
1. Forgetting self in Method Definitions
Wrong:
class Car:
def __init__(model, year): # Missing self!
self.model = model
Why it’s wrong: Python needs self as the first parameter to know which object to work with. Without it, you’ll get TypeError: __init__() takes 2 positional arguments but 3 were given.
Correct:
class Car:
def __init__(self, model, year):
self.model = model
self.year = year
2. Confusing Class Attributes with Instance Attributes
Wrong:
class Counter:
count = 0 # Class attribute (shared by all objects!)
def increment(self):
count += 1 # This creates a local variable, doesn't modify class attribute
Why it’s wrong: count without self. or Counter. is treated as a local variable. If you want each object to have its own counter, use self.count. If you want a shared counter across all objects, use Counter.count or self.__class__.count.
Correct (instance attribute):
class Counter:
def __init__(self):
self.count = 0 # Each object has its own count
def increment(self):
self.count += 1
3. Modifying Mutable Default Arguments
Wrong:
class Team:
def __init__(self, name, members=[]): # Dangerous!
self.name = name
self.members = members
team1 = Team("Red")
team1.members.append("Alice")
team2 = Team("Blue")
print(team2.members) # Output: ['Alice'] — unexpected!
Why it’s wrong: Default mutable arguments (lists, dictionaries) are created once when the class is defined, not each time an object is created. All objects share the same default list.
Correct:
class Team:
def __init__(self, name, members=None):
self.name = name
self.members = members if members is not None else []
4. Not Returning Values from Methods
Wrong:
class Calculator:
def add(self, a, b):
result = a + b # Calculated but not returned
calc = Calculator()
sum_value = calc.add(5, 3)
print(sum_value) # Output: None
Why it’s wrong: If you don’t explicitly return a value, Python returns None. This is confusing when you expect a result.
Correct:
class Calculator:
def add(self, a, b):
return a + b # Explicitly return the result
5. Creating Objects Without Calling the Constructor
Wrong:
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
my_book = Book # Missing parentheses!
print(my_book.title) # AttributeError: type object 'Book' has no attribute 'title'
Why it’s wrong: Book without parentheses refers to the class itself, not an object. You need Book(...) with arguments to create an object.
Correct:
my_book = Book("1984", "George Orwell")
print(my_book.title) # Output: 1984
Interview Tips
Be Ready to Design Classes On the Spot
Interviewers often ask: “Design a class to represent [X]” where X might be a parking lot, elevator system, or library book. Practice this framework:
- Identify attributes: What data does each object need to store?
- Identify methods: What actions can objects perform?
- Think about validation: What rules keep objects in valid states?
- Consider relationships: Does this class need to interact with other classes?
Example: “Design a Rectangle class.”
- Attributes:
width,height - Methods:
area(),perimeter(),is_square() - Validation: Width and height must be positive
Explain the Difference Between Class and Object Clearly
A common interview question: “What’s the difference between a class and an object?”
Strong answer: “A class is a blueprint that defines structure and behavior. An object is a specific instance created from that class with actual data. For example, Car is a class, but my_toyota with color=‘red’ and year=2020 is an object. Multiple objects can be created from one class, each with different attribute values.”
Weak answer: “A class is like a template and an object is like… a thing.”
Know Constructor Syntax Across Languages
If the job description mentions multiple languages, be ready to write constructors in each:
- Python:
def __init__(self, param): - Java:
public ClassName(Type param) { } - C++:
ClassName(Type param) { }(in class definition) - JavaScript:
constructor(param) { }
Mention: “In Python, we use __init__, but in Java, the constructor has the same name as the class.”
Demonstrate Understanding of self (or this)
Interviewers test whether you understand what self means. Be prepared to explain:
“The self parameter refers to the specific object instance calling the method. It’s how Python distinguishes between dog1.bark() and dog2.bark() — self points to dog1 in the first call and dog2 in the second. In Java and C++, this is called this.”
Show You Can Write Clean, Validated Constructors
Don’t just accept any input. Show you think about edge cases:
class Product:
def __init__(self, name, price):
if not name:
raise ValueError("Product name cannot be empty")
if price < 0:
raise ValueError("Price cannot be negative")
self.name = name
self.price = price
This demonstrates defensive programming and attention to data integrity — traits interviewers value.
Practice Common Class Design Questions
- Design a
Bookclass for a library system - Design a
BankAccountclass with deposit/withdraw - Design a
Studentclass with enrollment methods - Design a
ShoppingCartclass with add/remove items - Design a
ParkingLotclass with parking spots
For each, think: attributes, methods, validation, and how objects interact.
Key Takeaways
- Classes are blueprints that define structure (attributes) and behavior (methods); objects are instances created from classes with actual data.
- Constructors (
__init__in Python) initialize objects with starting values and ensure every object begins in a valid state. - Each object has its own copy of attributes but shares methods defined in the class — modifying one object doesn’t affect others.
- Use
self(Python) orthis(Java/C++) to refer to the current object instance within methods. - Validate inputs in constructors and methods to maintain object integrity and prevent invalid states.