Classes and Objects in OOP Explained

Updated 2026-03-11

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.

Prerequisites: Basic Python syntax including variables, functions, and data types (lists, dictionaries). Understanding of function parameters and return values. Familiarity with the concept of scope (local vs global variables).

After this topic: Define classes with attributes and methods, create multiple object instances from a single class, implement constructors to initialize object state, and explain the relationship between classes and objects in technical interviews.

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

  1. Encapsulation: Group related data and functions together
  2. Reusability: Write the class once, create many objects
  3. Modeling: Represent real-world entities in code
  4. 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:

  • self refers to the specific object being created or used
  • __init__ is called automatically when you write Dog("Buddy", 3)
  • Each object (dog1, dog2) has its own name and age
  • Methods are shared — both dogs use the same bark method

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.courses doesn’t affect student2.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:

  1. Identify attributes: What data does each object need to store?
  2. Identify methods: What actions can objects perform?
  3. Think about validation: What rules keep objects in valid states?
  4. 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 Book class for a library system
  • Design a BankAccount class with deposit/withdraw
  • Design a Student class with enrollment methods
  • Design a ShoppingCart class with add/remove items
  • Design a ParkingLot class 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) or this (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.