Menu
CoddyTech

OOPs in Python Interview Questions

Classes, inheritance and the dunder methods that make Python objects behave like built-ins.

12 questions
  1. What is a class and what is an object in Python?

    A class is a blueprint of attributes and methods; an object is one instance built from it, set up by __init__.

    Python
    class BankAccount:
        bank = "Coddy Bank"              # class attribute, shared
    
        def __init__(self, owner, balance=0):
            self.owner = owner           # instance attributes
            self.balance = balance
    
        def deposit(self, amount):
            self.balance += amount
            return self.balance
    
    a = BankAccount("Ravi")
    b = BankAccount("Asha", 500)
    a.deposit(100)
    print(a.owner, a.balance, b.owner, b.balance, a.bank)

    Each instance has its own owner and balance, while the class attribute bank is shared. See classes.

  2. What is self in Python?

    self is the instance a method was called on, passed automatically as the first argument, so a.deposit(100) is BankAccount.deposit(a, 100). It is a convention, not a keyword, and there is no implicit this, so attributes need self.balance.

  3. How do you make a variable private in Python?

    Python has no private keyword. _balance marks an attribute as internal by convention; __balance is name-mangled to _ClassName__balance, so subclasses do not overwrite it by accident.

    Python
    class Account:
        def __init__(self):
            self._internal = "by convention"
            self.__secret = 42
    
    acc = Account()
    print(acc._internal)
    try:
        print(acc.__secret)
    except AttributeError as e:
        print("AttributeError:", e)
    print(acc._Account__secret)

    Mangling prevents clashes, not access. For controlled access, use a @property.

  4. What is polymorphism in Python, and what is duck typing?

    Polymorphism means one interface with many implementations. In Python it mostly comes from duck typing: if an object has the method you call, it works, whatever its class.

    Python
    class Dog:
        def speak(self):
            return "Woof"
    
    class Robot:
        def speak(self):
            return "Beep"
    
    for thing in [Dog(), Robot()]:
        print(thing.speak())
    
    print(len("abc"), len([1, 2]), len({"a": 1}))

    Python has no overloading by signature; a second def with the same name replaces the first, so use default arguments or functools.singledispatch.

  5. What is the difference between __new__ and __init__?

    __new__ creates and returns the object; __init__ then initializes it and must return None.

    Python
    class Point:
        def __new__(cls, *args):
            print("__new__ called")
            return super().__new__(cls)
    
        def __init__(self, x, y):
            print("__init__ called")
            self.x, self.y = x, y
    
    p = Point(1, 2)
    print(p.x, p.y)

    You override __new__ mainly to subclass immutable types like int or tuple, or for singletons. If it returns something that is not an instance of the class, __init__ is skipped.

  6. Is a list defined in the class body shared between instances?

    Python
    class Basket:
        items = []
    
        def add(self, x):
            self.items.append(x)
    
    a = Basket()
    b = Basket()
    a.add(1)
    b.add(2)
    print(a.items)

    items = [] in the class body is one list shared by every instance, and self.items.append(x) mutates it, so both objects see both values. Create the list in __init__ with self.items = []. An assignment to self.items would create an instance attribute; only in-place mutation leaks.

  7. What is the difference between @staticmethod and @classmethod?

    A class method receives the class (cls), so it can build instances and read class state; a static method receives nothing implicit and is a plain function in the class namespace.

    Python
    class Date:
        def __init__(self, year, month, day):
            self.year, self.month, self.day = year, month, day
    
        @classmethod
        def from_string(cls, text):
            y, m, d = map(int, text.split("-"))
            return cls(y, m, d)
    
        @staticmethod
        def is_leap(year):
            return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
    
    d = Date.from_string("2024-02-29")
    print(d.year, d.month, d.day, Date.is_leap(2024))

    With cls, SubDate.from_string(...) returns a SubDate; a static method that hardcodes Date(...) could not.

  8. What is the difference between __str__ and __repr__?

    __str__ is the readable form used by print() and str(); __repr__ is the unambiguous developer form used by the REPL, repr() and containers.

    Python
    class User:
        def __init__(self, name):
            self.name = name
    
        def __repr__(self):
            return f"User(name={self.name!r})"
    
        def __str__(self):
            return self.name
    
    u = User("Asha")
    print(u)
    print(repr(u))
    print([u])

    A list prints the repr of its items, as the last line shows. If you implement only one, make it __repr__, because str() falls back to it.

  9. How do you create an abstract class or interface in Python?

    Inherit from abc.ABC and mark the required methods with @abstractmethod; a class with unimplemented abstract methods cannot be instantiated.

    Python
    from abc import ABC, abstractmethod
    
    class Shape(ABC):
        @abstractmethod
        def area(self):
            ...
    
    class Square(Shape):
        def __init__(self, side):
            self.side = side
    
        def area(self):
            return self.side ** 2
    
    print(Square(3).area())
    try:
        Shape()
    except TypeError as e:
        print("TypeError:", e)

    Python has no interface keyword, so an ABC with only abstract methods plays that role. typing.Protocol gives duck-typed interfaces without inheritance.

  10. What does the @property decorator do?

    @property turns a method into an attribute read without parentheses, and its .setter runs on assignment, usually to validate. It replaces get_x() and set_x() methods.

    Python
    class Temperature:
        def __init__(self, celsius):
            self.celsius = celsius
    
        @property
        def celsius(self):
            return self._celsius
    
        @celsius.setter
        def celsius(self, value):
            if value < -273.15:
                raise ValueError("below absolute zero")
            self._celsius = value
    
        @property
        def fahrenheit(self):
            return self._celsius * 9 / 5 + 32
    
    t = Temperature(25)
    print(t.fahrenheit)
    try:
        t.celsius = -300
    except ValueError as e:
        print("ValueError:", e)

    __init__ assigns through the setter, so the validation also applies on creation.

  11. In a diamond hierarchy, in what order does super() call the parents?

    Python
    class A:
        def hi(self):
            return "A"
    
    class B(A):
        def hi(self):
            return "B" + super().hi()
    
    class C(A):
        def hi(self):
            return "C" + super().hi()
    
    class D(B, C):
        def hi(self):
            return "D" + super().hi()
    
    print(D().hi())

    Python resolves methods along the MRO, computed with C3 linearization; here it is D, B, C, A, object (check D.__mro__). super() means the next class in the MRO of the object's actual type, not the parent, so super() inside B calls C.hi and A runs once. See inheritance.

  12. What are dataclasses in Python and when would you use one?

    @dataclass (Python 3.7+) generates __init__, __repr__ and __eq__ from the annotated fields. frozen=True makes instances immutable and hashable, and order=True adds comparisons.

    Python
    from dataclasses import dataclass, field
    
    @dataclass(frozen=True)
    class Point:
        x: int
        y: int
    
    @dataclass
    class Cart:
        owner: str
        items: list = field(default_factory=list)
    
    p = Point(1, 2)
    print(p, p == Point(1, 2))
    c = Cart("Ravi")
    c.items.append("book")
    print(c)

    Mutable defaults need field(default_factory=list); items: list = [] raises ValueError when the class is defined, which guards against the mutable default argument trap.

Coddy programming languages illustration

Learn to code with Coddy

Get started