Toolmingo
Guides27 min read

50 Object-Oriented Programming Interview Questions and Answers (2025)

Top 50 OOP interview questions with detailed answers covering encapsulation, inheritance, polymorphism, abstraction, SOLID principles, design patterns, and real-world examples in Python, Java, C++, and C#.

Object-oriented programming (OOP) is the foundation of most modern software development. Whether you're interviewing for a junior role or a senior engineering position, OOP questions test your grasp of fundamental design principles, language mechanics, and real-world application. This guide covers the 50 most common OOP interview questions with detailed answers and code examples.

OOP at a Glance

Pillar What it means Example
Encapsulation Bundle data + behavior; hide internals Private fields + public getters
Abstraction Expose what's needed, hide complexity Interface / abstract class
Inheritance Reuse behavior from parent class Dog extends Animal
Polymorphism Same interface, different behavior speak() differs per animal

Core OOP Concepts

1. What are the four pillars of OOP?

The four pillars are Encapsulation, Abstraction, Inheritance, and Polymorphism (EAIP).

  • Encapsulation — Binding data (attributes) and methods together while restricting direct access to internal state. Protects data integrity.
  • Abstraction — Hiding implementation complexity and exposing only what the caller needs to know.
  • Inheritance — A child class acquires properties and behaviors of a parent class, enabling code reuse.
  • Polymorphism — Objects of different types can be treated through a common interface; the right method is called at runtime.

2. What is encapsulation? Why is it important?

Encapsulation restricts access to an object's internal state and exposes a controlled interface.

class BankAccount:
    def __init__(self, balance: float):
        self.__balance = balance  # private

    def deposit(self, amount: float):
        if amount > 0:
            self.__balance += amount

    def get_balance(self) -> float:
        return self.__balance

account = BankAccount(1000)
account.deposit(500)
print(account.get_balance())  # 1500
# account.__balance = -999  # AttributeError — can't access directly

Why it matters:

  • Prevents invalid state (e.g., negative balance set directly)
  • Allows internal implementation to change without breaking callers
  • Easier debugging — state only changes through known methods

3. What is abstraction? Difference from encapsulation?

Abstraction hides what the implementation does (complexity). Encapsulation hides how data is stored (internals).

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float:  # abstraction — callers just call area()
        pass

class Circle(Shape):
    def __init__(self, radius: float):
        self.__radius = radius  # encapsulation — hides storage

    def area(self) -> float:
        return 3.14159 * self.__radius ** 2
Abstraction Encapsulation
Focus Design level — what to expose Implementation level — how to hide
Tool Abstract class, interface Private/protected fields
Goal Reduce complexity Protect data integrity

4. What is inheritance? Types of inheritance?

Inheritance allows a class (child/subclass) to inherit attributes and methods from another class (parent/superclass).

class Animal {
    protected String name;

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

    void speak() { System.out.println(name + " makes a sound"); }
}

class Dog extends Animal {
    Dog(String name) { super(name); }

    @Override
    void speak() { System.out.println(name + " barks"); }
}

Types:

Type Description Support
Single One parent, one child All OOP languages
Multilevel A→B→C chain All OOP languages
Hierarchical One parent, multiple children All OOP languages
Multiple One child, multiple parents C++, Python (not Java/C#)
Hybrid Mix of above C++, Python

Java/C# avoid multiple inheritance of classes to prevent the diamond problem — use interfaces instead.


5. What is polymorphism? Compile-time vs runtime?

Polymorphism = "many forms" — same method name behaves differently based on object type.

Compile-time (static) polymorphism — Method Overloading:

class MathUtils {
    int add(int a, int b) { return a + b; }
    double add(double a, double b) { return a + b; }  // different params
    int add(int a, int b, int c) { return a + b + c; }
}

Runtime (dynamic) polymorphism — Method Overriding:

Animal animal = new Dog("Rex");  // Dog reference in Animal variable
animal.speak();  // calls Dog.speak() — determined at runtime
Compile-time Runtime
Achieved by Method overloading Method overriding
Resolved at Compile time Runtime (dynamic dispatch)
Binding Static binding Dynamic binding
Performance Faster Slight overhead (vtable lookup)

6. What is a class vs an object?

  • Class — Blueprint/template defining attributes and methods. Exists at compile time.
  • Object — Instance of a class. Exists at runtime in memory.
class Car:              # class — blueprint
    def __init__(self, make, model):
        self.make = make
        self.model = model

    def describe(self):
        return f"{self.make} {self.model}"

my_car = Car("Toyota", "Camry")   # object — instance
your_car = Car("Honda", "Civic")  # another object from same class

7. What is an abstract class vs an interface?

Abstract Class Interface
Instantiation Cannot instantiate Cannot instantiate
Methods Can have concrete + abstract Abstract only (Java 7); default/static methods (Java 8+)
Fields Can have instance fields Constants only (Java)
Constructor Yes No
Inheritance Single (Java/C#) Multiple
Use when Partial shared implementation Define a contract
// Abstract class — partial implementation
abstract class Vehicle {
    protected int speed;
    abstract void accelerate();  // must override
    void stop() { speed = 0; }  // concrete — shared behavior
}

// Interface — pure contract
interface Flyable {
    void fly();
    default String mode() { return "air"; }  // Java 8+ default
}

class FlyingCar extends Vehicle implements Flyable {
    @Override public void accelerate() { speed += 10; }
    @Override public void fly() { System.out.println("Flying!"); }
}

8. What is method overloading vs method overriding?

Overloading (compile-time): Same method name, different parameters in the same class.

void print(int x) { }
void print(String s) { }
void print(int x, int y) { }  // same name, different signatures

Overriding (runtime): Same method signature in child class replaces parent's behavior.

class Animal { void speak() { System.out.println("..."); } }
class Cat extends Animal {
    @Override void speak() { System.out.println("Meow"); }
}
Overloading Overriding
Class Same class Parent + child class
Signature Must differ Must be identical
Return type Can differ Must match (or covariant)
Binding Static Dynamic
Polymorphism Compile-time Runtime

9. What is the super keyword?

super refers to the parent class. Used to:

  • Call parent constructor: super(args)
  • Call parent method: super.methodName()
  • Access parent field: super.fieldName
class Animal {
    String name;
    Animal(String name) { this.name = name; }
    void speak() { System.out.println("Sound from " + name); }
}

class Dog extends Animal {
    String breed;
    Dog(String name, String breed) {
        super(name);           // calls Animal constructor
        this.breed = breed;
    }

    @Override
    void speak() {
        super.speak();         // calls Animal.speak()
        System.out.println(name + " barks");
    }
}

10. What is the diamond problem?

The diamond problem occurs with multiple inheritance when two parent classes define the same method, and a child class inherits from both.

      A
     / \
    B   C       B.speak() and C.speak() both override A.speak()
     \ /
      D           Which speak() does D inherit?

Java/C# solve this by disallowing multiple class inheritance. Python uses MRO (Method Resolution Order) — C3 linearization algorithm.

class A:
    def speak(self): return "A"

class B(A):
    def speak(self): return "B"

class C(A):
    def speak(self): return "C"

class D(B, C): pass

print(D().speak())   # "B" — follows MRO: D → B → C → A
print(D.__mro__)     # (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, ...)

SOLID Principles

11. What are the SOLID principles?

SOLID is an acronym for five object-oriented design principles that make software more maintainable and extensible.

Letter Principle One-liner
S Single Responsibility A class has one reason to change
O Open/Closed Open for extension, closed for modification
L Liskov Substitution Subtypes must be substitutable for base types
I Interface Segregation Clients shouldn't depend on unused methods
D Dependency Inversion Depend on abstractions, not concretions

12. Explain Single Responsibility Principle (SRP)

A class should have one reason to change — it should do one thing well.

# WRONG — UserService handles 3 responsibilities
class UserService:
    def get_user(self, id): pass
    def save_to_database(self, user): pass   # DB responsibility
    def send_welcome_email(self, user): pass  # Email responsibility

# RIGHT — separate responsibilities
class UserRepository:
    def get_user(self, id): pass
    def save(self, user): pass

class EmailService:
    def send_welcome_email(self, user): pass

class UserService:
    def __init__(self, repo: UserRepository, email: EmailService):
        self.repo = repo
        self.email = email

13. Explain Open/Closed Principle (OCP)

Classes should be open for extension but closed for modification — add new behavior without changing existing code.

# WRONG — adding a new shape requires modifying AreaCalculator
class AreaCalculator:
    def calculate(self, shape):
        if shape.type == "circle": return 3.14 * shape.radius ** 2
        if shape.type == "rectangle": return shape.width * shape.height
        # every new shape = modify this class

# RIGHT — extend via polymorphism
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self) -> float: pass

class Circle(Shape):
    def __init__(self, r): self.r = r
    def area(self): return 3.14159 * self.r ** 2

class Rectangle(Shape):
    def __init__(self, w, h): self.w, self.h = w, h
    def area(self): return self.w * self.h

class Triangle(Shape):           # new shape — no existing code changes
    def __init__(self, b, h): self.b, self.h = b, h
    def area(self): return 0.5 * self.b * self.h

def total_area(shapes: list[Shape]) -> float:
    return sum(s.area() for s in shapes)

14. Explain Liskov Substitution Principle (LSP)

Objects of a subclass must be usable wherever the parent class is expected, without breaking the program.

# WRONG — Square violates LSP: setting width also sets height, breaks Rectangle contract
class Rectangle:
    def set_width(self, w): self.width = w
    def set_height(self, h): self.height = h
    def area(self): return self.width * self.height

class Square(Rectangle):
    def set_width(self, w): self.width = self.height = w  # breaks LSP
    def set_height(self, h): self.width = self.height = h

def area_should_be_width_times_height(r: Rectangle):
    r.set_width(5)
    r.set_height(4)
    assert r.area() == 20  # FAILS for Square — area is 16 (4*4)

# RIGHT — Square should NOT inherit Rectangle; both inherit Shape

LSP violation signs:

  • Subclass throws NotImplementedException for parent methods
  • Subclass weakens preconditions or strengthens postconditions
  • Code checks isinstance to handle subtype differently

15. Explain Interface Segregation Principle (ISP)

Clients should not be forced to depend on interfaces they don't use. Split fat interfaces into smaller, specific ones.

# WRONG — Robot must implement eat() which it cannot do
class Worker(ABC):
    @abstractmethod
    def work(self): pass
    @abstractmethod
    def eat(self): pass   # robots don't eat!

# RIGHT — segregate into focused interfaces
class Workable(ABC):
    @abstractmethod
    def work(self): pass

class Eatable(ABC):
    @abstractmethod
    def eat(self): pass

class HumanWorker(Workable, Eatable):
    def work(self): print("Working")
    def eat(self): print("Eating lunch")

class Robot(Workable):
    def work(self): print("Processing")
    # doesn't need to implement eat()

16. Explain Dependency Inversion Principle (DIP)

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend on details. Details should depend on abstractions.
# WRONG — OrderService depends on concrete MySQLDatabase
class MySQLDatabase:
    def save(self, order): pass

class OrderService:
    def __init__(self):
        self.db = MySQLDatabase()  # hardcoded dependency

# RIGHT — depend on abstraction
from abc import ABC, abstractmethod

class Database(ABC):
    @abstractmethod
    def save(self, order): pass

class MySQLDatabase(Database):
    def save(self, order): print("Saving to MySQL")

class MongoDatabase(Database):
    def save(self, order): print("Saving to MongoDB")

class OrderService:
    def __init__(self, db: Database):  # inject dependency
        self.db = db

# Swap databases without changing OrderService
service = OrderService(MySQLDatabase())
service = OrderService(MongoDatabase())

Classes and Objects Deep Dive

17. What is a constructor? Types?

A constructor is a special method called when an object is created, used to initialize object state.

class Person {
    String name;
    int age;

    // Default constructor
    Person() { this.name = "Unknown"; this.age = 0; }

    // Parameterized constructor
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Copy constructor
    Person(Person other) {
        this.name = other.name;
        this.age = other.age;
    }
}
Type When used
Default No-arg; sets default values
Parameterized Initialize with provided values
Copy Create a copy of another object (C++)
Static factory Named alternatives to constructors

18. What is the this keyword?

this refers to the current instance of the class.

class Employee {
    String name;
    double salary;

    Employee(String name, double salary) {
        this.name = name;      // disambiguates from parameter
        this.salary = salary;
    }

    Employee promote(double raise) {
        this.salary += raise;
        return this;           // method chaining
    }

    void printInfo() {
        System.out.println(this.name + ": $" + this.salary);
    }
}

Uses of this:

  • Differentiate instance variable from constructor/method parameter
  • Call another constructor from the same class (this(args))
  • Return current object for method chaining
  • Pass current object as argument to another method

19. What is static vs instance members?

Instance Static
Belongs to Object (instance) Class itself
Memory Per object Once per class
Access Via object reference Via class name
Use this Yes No
When to use Object state/behavior Shared state, utilities
class Counter:
    count = 0          # static — shared across all instances

    def __init__(self):
        Counter.count += 1
        self.id = Counter.count  # instance — unique per object

c1 = Counter()   # Counter.count = 1, c1.id = 1
c2 = Counter()   # Counter.count = 2, c2.id = 2
print(Counter.count)  # 2 — class-level

20. What is an inner class? Types?

An inner class is a class defined inside another class.

class Outer {
    private int x = 10;

    // Non-static inner class — has access to outer instance
    class Inner {
        void display() { System.out.println(x); }
    }

    // Static nested class — no access to outer instance fields
    static class StaticNested {
        void greet() { System.out.println("Static nested"); }
    }

    void method() {
        // Local inner class — defined inside a method
        class Local {
            void run() { System.out.println("Local class"); }
        }
        new Local().run();
    }

    Runnable getLambda() {
        // Anonymous class — inline implementation
        return new Runnable() {
            @Override public void run() { System.out.println("Anonymous"); }
        };
    }
}

21. What is composition vs inheritance? When to use each?

Favor composition over inheritance — Gang of Four

Inheritance ("is-a"): Dog is an Animal Composition ("has-a"): Car has an Engine

# Inheritance — works when true "is-a" relationship
class Animal:
    def breathe(self): return "breathing"

class Dog(Animal):
    def bark(self): return "woof"

# Composition — preferred for flexibility
class Engine:
    def start(self): return "vroom"

class Car:
    def __init__(self):
        self.engine = Engine()  # has-a

    def drive(self):
        return self.engine.start()

Use inheritance when:

  • True "is-a" relationship exists
  • Child truly represents a specialization of parent
  • Liskov Substitution holds

Use composition when:

  • "Has-a" or "uses-a" relationship
  • Need flexibility to swap implementations
  • Inheritance would create fragile coupling

22. What is method hiding vs method overriding?

class Parent {
    static void staticMethod() { System.out.println("Parent static"); }
    void instanceMethod() { System.out.println("Parent instance"); }
}

class Child extends Parent {
    // Method HIDING — static methods are NOT overridden, they're hidden
    static void staticMethod() { System.out.println("Child static"); }

    // Method OVERRIDING — dynamic dispatch applies
    @Override
    void instanceMethod() { System.out.println("Child instance"); }
}

Parent ref = new Child();
ref.staticMethod();   // "Parent static" — resolved at compile time (hiding)
ref.instanceMethod(); // "Child instance" — resolved at runtime (overriding)

23. What are access modifiers?

Modifier Class Package Subclass World
public
protected
(default)
private

Best practice: use the most restrictive access possible (principle of least privilege).


24. What is a sealed/final class?

A final (Java/C#: sealed) class cannot be subclassed. Used when inheritance would break invariants.

public final class String { }   // Java String is final
// class MyString extends String { }  // compile error

// C#
public sealed class Singleton { }

Why seal a class?

  • Security — prevent malicious subclasses
  • Performance — JIT can inline calls (no vtable)
  • Immutability guarantees (e.g., java.lang.String)

25. What is object cloning? Shallow vs deep copy?

class Address {
    String city;
    Address(String city) { this.city = city; }
}

class Person implements Cloneable {
    String name;
    Address address;

    Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    // Shallow copy — copies references
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();  // address reference is shared!
    }

    // Deep copy — copies nested objects
    Person deepCopy() {
        return new Person(this.name, new Address(this.address.city));
    }
}

Person original = new Person("Alice", new Address("NYC"));
Person shallow = (Person) original.clone();
shallow.address.city = "LA";  // ALSO changes original.address.city!

Person deep = original.deepCopy();
deep.address.city = "Chicago";  // does NOT affect original

Design Patterns

26. What is the Singleton pattern? How to implement thread-safely?

Ensures a class has only one instance and provides a global access point.

// Thread-safe Singleton using initialization-on-demand holder
public class DatabaseConnection {
    private DatabaseConnection() { }

    private static class Holder {
        static final DatabaseConnection INSTANCE = new DatabaseConnection();
    }

    public static DatabaseConnection getInstance() {
        return Holder.INSTANCE;  // JVM guarantees thread-safe initialization
    }

    public void query(String sql) { /* ... */ }
}

// Usage
DatabaseConnection.getInstance().query("SELECT * FROM users");

Anti-pattern warning: Singletons introduce global state and make testing harder. Prefer dependency injection.


27. What is the Factory pattern?

Factory method pattern delegates object creation to subclasses.

from abc import ABC, abstractmethod

class Notification(ABC):
    @abstractmethod
    def send(self, message: str): pass

class EmailNotification(Notification):
    def send(self, message: str): print(f"Email: {message}")

class SMSNotification(Notification):
    def send(self, message: str): print(f"SMS: {message}")

class PushNotification(Notification):
    def send(self, message: str): print(f"Push: {message}")

class NotificationFactory:
    @staticmethod
    def create(type_: str) -> Notification:
        options = {
            "email": EmailNotification,
            "sms": SMSNotification,
            "push": PushNotification,
        }
        cls = options.get(type_)
        if not cls:
            raise ValueError(f"Unknown notification type: {type_}")
        return cls()

notif = NotificationFactory.create("email")
notif.send("Hello!")

28. What is the Observer pattern?

Defines a one-to-many dependency so when one object changes state, all dependents are notified automatically.

from abc import ABC, abstractmethod

class Observer(ABC):
    @abstractmethod
    def update(self, event: str): pass

class EventSource:
    def __init__(self):
        self._observers: list[Observer] = []

    def subscribe(self, observer: Observer):
        self._observers.append(observer)

    def unsubscribe(self, observer: Observer):
        self._observers.remove(observer)

    def notify(self, event: str):
        for observer in self._observers:
            observer.update(event)

class Logger(Observer):
    def update(self, event: str): print(f"[LOG] {event}")

class AlertService(Observer):
    def update(self, event: str): print(f"[ALERT] {event}")

source = EventSource()
source.subscribe(Logger())
source.subscribe(AlertService())
source.notify("User logged in")  # both receive event

Real-world use: Event systems, UI frameworks (MVC), pub/sub message queues.


29. What is the Strategy pattern?

Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

from abc import ABC, abstractmethod

class SortStrategy(ABC):
    @abstractmethod
    def sort(self, data: list) -> list: pass

class BubbleSort(SortStrategy):
    def sort(self, data): return sorted(data)  # simplified

class QuickSort(SortStrategy):
    def sort(self, data): return sorted(data, key=lambda x: x)

class Sorter:
    def __init__(self, strategy: SortStrategy):
        self._strategy = strategy

    def set_strategy(self, strategy: SortStrategy):
        self._strategy = strategy

    def sort(self, data: list) -> list:
        return self._strategy.sort(data)

sorter = Sorter(BubbleSort())
sorter.sort([3, 1, 2])

sorter.set_strategy(QuickSort())  # swap algorithm without changing Sorter
sorter.sort([3, 1, 2])

30. What is the Decorator pattern?

Attaches additional responsibilities to an object dynamically without modifying its class.

from abc import ABC, abstractmethod

class Coffee(ABC):
    @abstractmethod
    def cost(self) -> float: pass
    @abstractmethod
    def description(self) -> str: pass

class Espresso(Coffee):
    def cost(self) -> float: return 1.50
    def description(self) -> str: return "Espresso"

class MilkDecorator(Coffee):
    def __init__(self, coffee: Coffee):
        self._coffee = coffee
    def cost(self) -> float: return self._coffee.cost() + 0.25
    def description(self) -> str: return self._coffee.description() + ", Milk"

class VanillaDecorator(Coffee):
    def __init__(self, coffee: Coffee):
        self._coffee = coffee
    def cost(self) -> float: return self._coffee.cost() + 0.50
    def description(self) -> str: return self._coffee.description() + ", Vanilla"

drink = VanillaDecorator(MilkDecorator(Espresso()))
print(drink.description())  # Espresso, Milk, Vanilla
print(drink.cost())         # 2.25

Advanced OOP Concepts

31. What is a mixin?

A mixin is a class providing methods to other classes through multiple inheritance without being a standalone class.

class JSONMixin:
    def to_json(self):
        import json
        return json.dumps(self.__dict__)

class LogMixin:
    def log(self, message: str):
        print(f"[{self.__class__.__name__}] {message}")

class User(JSONMixin, LogMixin):
    def __init__(self, name: str, email: str):
        self.name = name
        self.email = email

user = User("Alice", "alice@example.com")
print(user.to_json())      # {"name": "Alice", "email": "alice@example.com"}
user.log("User created")   # [User] User created

Mixins are widely used in Python (Django class-based views), Ruby, and other languages.


32. What is duck typing?

"If it walks like a duck and quacks like a duck, it's a duck." — Python checks for behavior, not type.

class Dog:
    def speak(self): return "Woof"

class Cat:
    def speak(self): return "Meow"

class Robot:
    def speak(self): return "Beep boop"

def make_speak(entity):
    print(entity.speak())  # no isinstance check needed

for speaker in [Dog(), Cat(), Robot()]:
    make_speak(speaker)  # works as long as speak() exists

33. What is method resolution order (MRO)?

MRO determines the order Python searches for a method in the inheritance hierarchy using the C3 linearization algorithm.

class A:
    def hello(self): return "A"

class B(A):
    def hello(self): return "B"

class C(A):
    def hello(self): return "C"

class D(B, C):
    pass

print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
print(D().hello())  # "B" — follows MRO order

34. What are magic/dunder methods?

Special methods surrounded by double underscores (__) that Python calls implicitly.

class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __len__(self):
        return 2

    def __iter__(self):
        yield self.x
        yield self.y

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)     # Vector(4, 6) — calls __add__
print(v1 == v1)    # True — calls __eq__
print(len(v1))     # 2 — calls __len__
x, y = v1          # unpacking — calls __iter__

Common dunder methods:

Method When called
__init__ Object creation
__str__ str(obj)
__repr__ repr(obj), debugging
__len__ len(obj)
__eq__ obj1 == obj2
__lt__ obj1 < obj2
__hash__ hash(obj), dict keys
__enter__/__exit__ with statement
__iter__/__next__ iteration
__getitem__ obj[key]
__call__ obj()

35. What is an abstract method vs virtual method?

// C#
abstract class Animal {
    // Abstract — no implementation, MUST override in subclass
    public abstract void Speak();

    // Virtual — has default implementation, CAN override
    public virtual void Breathe() => Console.WriteLine("Breathing");
}

class Dog : Animal {
    public override void Speak() => Console.WriteLine("Woof");
    // Breathe() — can inherit parent's or override
}
Abstract Virtual
Implementation None Has default
Override required Yes No
In abstract class Must be abstract Can be in any class

36. What is covariance and contravariance?

  • Covariance — return type can be more derived type (child)
  • Contravariance — parameter type can be more base type (parent)
class Animal { }
class Dog extends Animal { }

class AnimalFactory {
    Animal create() { return new Animal(); }
}

class DogFactory extends AnimalFactory {
    @Override
    Dog create() { return new Dog(); }  // covariant return type — allowed in Java
}

In generics, Java uses wildcards: ? extends T (covariant), ? super T (contravariant).


37. What is a fluent interface / method chaining?

A pattern where methods return this to allow chaining multiple calls.

class QueryBuilder:
    def __init__(self):
        self._table = ""
        self._conditions = []
        self._limit = None

    def from_table(self, table: str) -> "QueryBuilder":
        self._table = table
        return self

    def where(self, condition: str) -> "QueryBuilder":
        self._conditions.append(condition)
        return self

    def limit(self, n: int) -> "QueryBuilder":
        self._limit = n
        return self

    def build(self) -> str:
        sql = f"SELECT * FROM {self._table}"
        if self._conditions:
            sql += " WHERE " + " AND ".join(self._conditions)
        if self._limit:
            sql += f" LIMIT {self._limit}"
        return sql

query = (QueryBuilder()
    .from_table("users")
    .where("age > 18")
    .where("active = 1")
    .limit(10)
    .build())
# SELECT * FROM users WHERE age > 18 AND active = 1 LIMIT 10

38. What is dependency injection (DI)?

DI is a design pattern where dependencies are provided to an object rather than created within it.

# Without DI — hard to test, hard to swap implementations
class OrderService:
    def __init__(self):
        self.db = ProductionDatabase()   # hardcoded!

# With DI — testable, flexible
class OrderService:
    def __init__(self, db: Database):   # injected
        self.db = db

# Production
service = OrderService(MySQLDatabase())

# Test
service = OrderService(InMemoryDatabase())  # no production DB needed

Types of injection:

  • Constructor injection — via constructor (preferred)
  • Setter injection — via setter method
  • Interface injection — via interface method

39. What is the Law of Demeter (LoD)?

"Don't talk to strangers." A method should only call methods on:

  • Itself (this)
  • Its direct fields
  • Parameters passed to it
  • Objects it creates
# WRONG — violates LoD (train wreck)
def get_city(customer):
    return customer.get_address().get_location().get_city()

# RIGHT — tell, don't ask
def get_city(customer):
    return customer.get_city()   # Customer knows its city

class Customer:
    def get_city(self):
        return self._address.location.city  # internalized

40. What is object cohesion and coupling?

Good Bad
Cohesion High — class does one focused thing Low — class does many unrelated things
Coupling Loose — classes have minimal dependencies Tight — changes in one class break others

Goal: High cohesion, loose coupling.

# Tight coupling — PaymentService knows MySQL internals
class PaymentService:
    def pay(self, amount):
        import mysql.connector
        conn = mysql.connector.connect(host="localhost", ...)

# Loose coupling — PaymentService depends on abstraction
class PaymentService:
    def __init__(self, db: Database):
        self.db = db
    def pay(self, amount):
        self.db.save({"amount": amount})

Practical & Language-Specific Questions

41. How does Python handle multiple inheritance?

Python uses MRO (C3 linearization) — left-to-right, depth-first, with consistency constraints.

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass

# MRO: D → B → C → A → object

super() respects MRO — always calls the next class in MRO, not necessarily the direct parent.


42. What is @property in Python?

Allows method access via attribute syntax while maintaining encapsulation.

class Temperature:
    def __init__(self, celsius: float):
        self._celsius = celsius

    @property
    def celsius(self) -> float:
        return self._celsius

    @celsius.setter
    def celsius(self, value: float):
        if value < -273.15:
            raise ValueError("Temperature below absolute zero!")
        self._celsius = value

    @property
    def fahrenheit(self) -> float:
        return self._celsius * 9/5 + 32

t = Temperature(25)
print(t.fahrenheit)  # 77.0 — accessed like attribute, not t.fahrenheit()
t.celsius = -300     # raises ValueError

43. What is the difference between __str__ and __repr__?

class User:
    def __init__(self, name, id):
        self.name, self.id = name, id

    def __str__(self):
        return f"User: {self.name}"     # human-readable

    def __repr__(self):
        return f"User(name={self.name!r}, id={self.id!r})"  # unambiguous, for debugging

u = User("Alice", 42)
print(str(u))     # "User: Alice"
print(repr(u))    # "User(name='Alice', id=42)"
print([u])        # Uses repr in containers: [User(name='Alice', id=42)]

44. How is OOP different in Python vs Java?

Feature Python Java
Access modifiers Convention (_, __) Enforced by compiler
Multiple inheritance Yes No (interfaces only)
Duck typing Yes No (static typed)
Abstract classes abc.ABC abstract keyword
Properties @property Getters/setters
Metaclasses Yes Limited
All classes inherit object Object

45. What is object serialization?

Converting an object to a format that can be stored or transmitted (JSON, XML, binary), and reconstructing it.

import json
from dataclasses import dataclass, asdict

@dataclass
class User:
    name: str
    age: int
    email: str

user = User("Alice", 30, "alice@example.com")

# Serialize
data = json.dumps(asdict(user))
print(data)  # '{"name": "Alice", "age": 30, "email": "alice@example.com"}'

# Deserialize
user2 = User(**json.loads(data))

Java uses implements Serializable. C# uses [Serializable] attribute.


46. What is the difference between == and .equals() in Java?

String a = new String("hello");
String b = new String("hello");

System.out.println(a == b);         // false — different object references
System.out.println(a.equals(b));    // true — same content

// But for string literals (string pool):
String c = "hello";
String d = "hello";
System.out.println(c == d);         // true — same pool object

Rule: Always use .equals() for content comparison. == checks reference equality.


47. What is a generic class?

Generics allow writing type-safe code that works with different types without casting.

// Without generics — unsafe, requires casting
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);  // ClassCastException at runtime if wrong

// With generics — type-safe at compile time
List<String> strings = new ArrayList<>();
strings.add("hello");
String str = strings.get(0);  // no cast needed

// Generic class
class Pair<A, B> {
    private final A first;
    private final B second;
    Pair(A first, B second) { this.first = first; this.second = second; }
    A getFirst() { return first; }
    B getSecond() { return second; }
}

Pair<String, Integer> pair = new Pair<>("age", 30);

48. What are some OOP best practices?

Practice Description
Favor composition over inheritance More flexible, less coupling
Program to interfaces Depend on abstractions
Keep classes small SRP — one reason to change
Avoid deep inheritance 2-3 levels max
Immutability Make fields final/readonly where possible
Encapsulate what varies Isolate change-prone code
Tell, don't ask Tell objects to do things, don't query and decide
Least knowledge (LoD) Only talk to immediate friends

49. What OOP patterns are commonly used in real-world applications?

Pattern Real-world use
Singleton DB connection pools, config, loggers
Factory/Abstract Factory UI widget creation, payment processors
Builder SQL query builders, HTTP request builders
Observer Event systems, UI frameworks, pub/sub
Strategy Sorting, payment methods, compression algorithms
Decorator Middleware stacks, logging wrappers, I/O streams
Repository Data access layer, ORM abstractions
Command Undo/redo, job queues, transactional scripts
Proxy Lazy loading, caching, access control
Facade Simplifying complex subsystems (e.g., API clients)

50. How do you design an OOP solution? (Common interview scenario)

Design a Parking Lot system:

from enum import Enum
from abc import ABC, abstractmethod
from datetime import datetime

class VehicleType(Enum):
    MOTORCYCLE, CAR, TRUCK = "motorcycle", "car", "truck"

class Vehicle:
    def __init__(self, plate: str, type_: VehicleType):
        self.plate = plate
        self.type = type_

class ParkingSpot:
    def __init__(self, id: str, type_: VehicleType):
        self.id = id
        self.type = type_
        self.vehicle: Vehicle | None = None

    def is_available(self) -> bool:
        return self.vehicle is None

    def park(self, vehicle: Vehicle):
        if not self.is_available():
            raise RuntimeError("Spot occupied")
        self.vehicle = vehicle

    def vacate(self) -> Vehicle:
        vehicle = self.vehicle
        self.vehicle = None
        return vehicle

class PricingStrategy(ABC):
    @abstractmethod
    def calculate(self, hours: float, vehicle_type: VehicleType) -> float: pass

class HourlyPricing(PricingStrategy):
    RATES = {VehicleType.MOTORCYCLE: 1.0, VehicleType.CAR: 2.0, VehicleType.TRUCK: 3.5}
    def calculate(self, hours: float, vehicle_type: VehicleType) -> float:
        return hours * self.RATES[vehicle_type]

class ParkingLot:
    def __init__(self, spots: list[ParkingSpot], pricing: PricingStrategy):
        self.spots = spots
        self.pricing = pricing
        self._tickets: dict[str, tuple[ParkingSpot, datetime]] = {}

    def park(self, vehicle: Vehicle) -> str:
        spot = next((s for s in self.spots
                     if s.type == vehicle.type and s.is_available()), None)
        if not spot:
            raise RuntimeError("No available spots")
        spot.park(vehicle)
        self._tickets[vehicle.plate] = (spot, datetime.now())
        return spot.id

    def exit(self, plate: str) -> float:
        spot, entry_time = self._tickets.pop(plate)
        vehicle = spot.vacate()
        hours = (datetime.now() - entry_time).seconds / 3600
        return self.pricing.calculate(max(hours, 1/60), vehicle.type)

Approach for any OOP design question:

  1. Identify entities (nouns) → classes
  2. Identify behaviors (verbs) → methods
  3. Define relationships (is-a vs has-a)
  4. Apply SOLID principles
  5. Use design patterns where appropriate

Anti-Patterns to Avoid

Anti-pattern Problem Fix
God Object One class knows/does everything SRP — split responsibilities
Anemic Domain Model Classes with only data, no behavior Add domain logic to entities
Deep Inheritance 5+ levels of inheritance Prefer composition, max 2-3 levels
Inappropriate Intimacy Classes know too much about each other Encapsulate, use interfaces
Refused Bequest Subclass doesn't use inherited members Violates LSP — restructure
Premature Generalization Over-engineered for hypothetical future YAGNI — do what's needed
Spaghetti Inheritance Multiple inheritance misused Use interfaces + composition
Singleton Abuse Global state everywhere Use dependency injection

OOP vs Other Paradigms

Paradigm Key Concept Best for
OOP Objects, encapsulation, polymorphism Business apps, UI, games
Functional Pure functions, immutability, composition Data transforms, concurrent systems
Procedural Sequences of instructions Scripts, simple programs
Reactive Data streams, async propagation UI events, real-time data

Modern languages blend paradigms — Python, Kotlin, and Scala support both OOP and functional styles.


FAQ

Q: Is everything in Python an object?

Yes. Every value in Python — integers, strings, functions, classes — is an object. type(42)<class 'int'>. Even None is an object (<class 'NoneType'>).


Q: Can you override a private method?

No. Private methods are not visible to subclasses, so there's nothing to override. In Python, name mangling (__method_ClassName__method) prevents accidental access but doesn't prevent override if you know the mangled name.


Q: What's the difference between aggregation and composition?

Both are "has-a" relationships:

  • Aggregation — weak ownership. Parts can exist without the whole. Department has Employees — employees survive if department is disbanded.
  • Composition — strong ownership. Parts cannot exist without the whole. House has Rooms — rooms don't exist without the house.

Q: When should you NOT use OOP?

  • Simple scripts with no shared state
  • Pure data pipelines and transformations (functional style fits better)
  • Performance-critical systems where vtable dispatch overhead matters
  • Configuration/glue code

Q: How do you explain OOP to a non-technical person?

OOP is like building with LEGO bricks. Each brick (object) has a specific shape and purpose. You can reuse the same brick in different builds (code reuse). Some bricks snap together only in certain ways (encapsulation). You can build a bigger, specialized brick on top of a basic one (inheritance). The same colored brick can play different roles in different builds (polymorphism).


Q: What's the best way to practice OOP design?

  1. Design common systems: Parking lot, Library, ATM, Chess game, Hotel booking
  2. Refactor procedural code into OOP
  3. Implement design patterns from scratch
  4. Read open-source OOP codebases (Spring, Django)
  5. Do LeetCode Object-Oriented Design problems

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools