Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

OOP & Classes
PYTHON

OOP & Classes

Practice 8 Python questions covering classes, objects, inheritance, MRO, attributes, methods, and object-oriented programming.

1. What does the @classmethod decorator pass automatically as its first parameter?

Answer: The class object itself, conventionally named cls.

The three method kinds differ in what they bind as the first parameter:

  • A normal instance method receives the instance — self. It can access and modify instance state.
  • A @classmethod receives the classcls. It doesn’t need an instance to be called; MyClass.my_method() works, and so does instance.my_method(). Since it has the class, it can access class attributes and create instances, but it can’t touch instance state.
  • A @staticmethod receives neither — it’s just a plain function sitting inside the class namespace.

The practical difference between classmethod and staticmethod: the class method knows what class it belongs to. That matters for polymorphism — a class method called on a subclass receives the subclass, so a factory like MyClass.from_string(s) will correctly build instances of whatever subclass it’s called on. A static method has no such awareness.

The interview answer: @classmethod passes cls — the class object.

Answer:

The class object itself, conventionally named cls.

The three method kinds differ in what they bind as the first parameter:

  • A normal instance method receives the instance — self. It can access and modify instance state.
  • A @classmethod receives the classcls. It doesn’t need an instance to be called; MyClass.my_method() works, and so does instance.my_method(). Since it has the class, it can access class attributes and create instances, but it can’t touch instance state.
  • A @staticmethod receives neither — it’s just a plain function sitting inside the class namespace.

The practical difference between classmethod and staticmethod: the class method knows what class it belongs to. That matters for polymorphism — a class method called on a subclass receives the subclass, so a factory like MyClass.from_string(s) will correctly build instances of whatever subclass it’s called on. A static method has no such awareness.

The interview answer: @classmethod passes cls — the class object.

2. What is the purpose of the slots declaration in a Python class?

Answer: __slots__ optimizes memory by preventing the automatic creation of a per-instance __dict__, fixing the set of allowed attributes in advance.

Normally every Python object carries an instance dictionary, __dict__, which stores its attributes. That dict is flexible but heavy — it’s a whole hash table per instance. When you’re creating millions of instances (say, in data processing), that overhead dominates memory.

Declaring __slots__ = ('x', 'y') tells Python: instances may only have these attributes, and they will be stored in compact internal descriptors instead of a __dict__. The instance dict is never created, so each object gets dramatically smaller.

Two consequences follow:

  • Memory savings — the headline benefit; can be substantial at scale.
  • Restricted attributes — you can no longer assign arbitrary attributes; obj.z = 1 raises AttributeError when z isn’t in __slots__. That’s usually fine, since you declared what you need.

The trade-off is flexibility for memory. The interview answer: __slots__ removes the per-instance __dict__ to save memory, at the cost of a fixed attribute set.

Answer:

__slots__ optimizes memory by preventing the automatic creation of a per-instance __dict__, fixing the set of allowed attributes in advance.

Normally every Python object carries an instance dictionary, __dict__, which stores its attributes. That dict is flexible but heavy — it’s a whole hash table per instance. When you’re creating millions of instances (say, in data processing), that overhead dominates memory.

Declaring __slots__ = ('x', 'y') tells Python: instances may only have these attributes, and they will be stored in compact internal descriptors instead of a __dict__. The instance dict is never created, so each object gets dramatically smaller.

Two consequences follow:

  • Memory savings — the headline benefit; can be substantial at scale.
  • Restricted attributes — you can no longer assign arbitrary attributes; obj.z = 1 raises AttributeError when z isn’t in __slots__. That’s usually fine, since you declared what you need.

The trade-off is flexibility for memory. The interview answer: __slots__ removes the per-instance __dict__ to save memory, at the cost of a fixed attribute set.

3. What is the output of the following code?

def func(a, b=5, *args, **kwargs):
    print(len(args), len(kwargs))

func(1, 2, 3, 4, x=5, y=6)

Output: 2 2

Trace the argument binding carefully.

  • a = 1 — first positional.
  • b = 5 is the default, but the call passes 2 as the second positional, so b = 2.
  • *args collects the remaining positional arguments — 3 and 4. That’s 2 items.
  • **kwargs collects all keyword arguments not matched by named parameters — x=5 and y=6. That’s 2 items.

So len(args) is 2 and len(kwargs) is 2. Output: 2 2.

The trap is counting wrong: a and b soak up the first two positionals, leaving (3, 4) for args, while the two keyword arguments x and y go to kwargs. Both lengths are 2.

Answer:

2 2

Trace the argument binding carefully.

  • a = 1 — first positional.
  • b = 5 is the default, but the call passes 2 as the second positional, so b = 2.
  • *args collects the remaining positional arguments — 3 and 4. That’s 2 items.
  • **kwargs collects all keyword arguments not matched by named parameters — x=5 and y=6. That’s 2 items.

So len(args) is 2 and len(kwargs) is 2. Output: 2 2.

The trap is counting wrong: a and b soak up the first two positionals, leaving (3, 4) for args, while the two keyword arguments x and y go to kwargs. Both lengths are 2.

4. Which dunder method is invoked when evaluating len(instance)?

Answer: __len__.

len(obj) doesn’t directly read a size field — it dispatches to the object’s __len__ method.

When you call len(x), Python internally invokes x.__len__() and returns the integer result. This is the protocol pattern that powers most built-in functions: len__len__, str__str__, +__add__, ==__eq__, and so on.

For custom classes, defining __len__ gives you two things at once: len(obj) starts working, and the object becomes truthy/falsy based on its length in boolean contexts (an object with __len__ returning 0 is falsy).

The interview answer: len(instance) calls instance.__len__().

Answer:

__len__.

len(obj) doesn’t directly read a size field — it dispatches to the object’s __len__ method.

When you call len(x), Python internally invokes x.__len__() and returns the integer result. This is the protocol pattern that powers most built-in functions: len__len__, str__str__, +__add__, ==__eq__, and so on.

For custom classes, defining __len__ gives you two things at once: len(obj) starts working, and the object becomes truthy/falsy based on its length in boolean contexts (an object with __len__ returning 0 is falsy).

The interview answer: len(instance) calls instance.__len__().

5. How does Method Resolution Order (MRO) handle class resolution in Python 3?

Answer: Python uses the C3 Linearization algorithm to compute the MRO.

When you access a method on an instance, Python must decide which class in the inheritance hierarchy provides it. The MRO is the ordered list of classes searched, and C3 is the algorithm that builds it.

C3 produces a linearization with three properties:

  • Subclasses come before their parents.
  • The order respects each base class’s own MRO.
  • A class never appears more than once.

The result avoids the problems of naive depth-first search in diamond inheritance — where class D(C, B) and both C and B inherit from A. A naive DFS could reach A twice or visit a subclass through the wrong branch. C3 guarantees a consistent, sensible order: roughly, D, then C’s line, then B’s line, with A last.

You can always inspect the order directly with ClassName.__mro__ or ClassName.mro(). If a hierarchy can’t be linearized consistently, Python raises a TypeError at class definition time.

The interview answer: Python 3 resolves methods with the C3 linearization algorithm, which builds a consistent single ordering that respects parent orders and handles diamond inheritance correctly.

Answer:

Python uses the C3 Linearization algorithm to compute the MRO.

When you access a method on an instance, Python must decide which class in the inheritance hierarchy provides it. The MRO is the ordered list of classes searched, and C3 is the algorithm that builds it.

C3 produces a linearization with three properties:

  • Subclasses come before their parents.
  • The order respects each base class’s own MRO.
  • A class never appears more than once.

The result avoids the problems of naive depth-first search in diamond inheritance — where class D(C, B) and both C and B inherit from A. A naive DFS could reach A twice or visit a subclass through the wrong branch. C3 guarantees a consistent, sensible order: roughly, D, then C’s line, then B’s line, with A last.

You can always inspect the order directly with ClassName.__mro__ or ClassName.mro(). If a hierarchy can’t be linearized consistently, Python raises a TypeError at class definition time.

The interview answer: Python 3 resolves methods with the C3 linearization algorithm, which builds a consistent single ordering that respects parent orders and handles diamond inheritance correctly.

6. What happens when calling super().init() in a child class?

Answer: It invokes the parent (or next-in-line) class initializer, following the class’s MRO.

super() doesn’t literally mean “the parent class” in the naive sense. It returns a proxy that resolves to the next class in the instance’s Method Resolution Order — which is usually the direct parent, but in cooperative multiple inheritance can be a sibling class.

Two things happen when you call it:

  • The parent’s __init__ runs, initializing the inherited state so the child can build on top of it.
  • Which __init__ actually runs is decided by the MRO, not by reading the source. This matters in diamond hierarchies where super() chains through multiple classes.

The rules are strict: super().__init__() should be called with the arguments the parent expects, and it’s typically the first thing a child’s __init__ does. Calling it makes the child’s initialization complete instead of leaving inherited attributes unset.

The interview answer: super() returns an MRO-resolved proxy; calling __init__ through it invokes the next class’s initializer in the resolution order, not blindly “the parent.”

Answer:

It invokes the parent (or next-in-line) class initializer, following the class’s MRO.

super() doesn’t literally mean “the parent class” in the naive sense. It returns a proxy that resolves to the next class in the instance’s Method Resolution Order — which is usually the direct parent, but in cooperative multiple inheritance can be a sibling class.

Two things happen when you call it:

  • The parent’s __init__ runs, initializing the inherited state so the child can build on top of it.
  • Which __init__ actually runs is decided by the MRO, not by reading the source. This matters in diamond hierarchies where super() chains through multiple classes.

The rules are strict: super().__init__() should be called with the arguments the parent expects, and it’s typically the first thing a child’s __init__ does. Calling it makes the child’s initialization complete instead of leaving inherited attributes unset.

The interview answer: super() returns an MRO-resolved proxy; calling __init__ through it invokes the next class’s initializer in the resolution order, not blindly “the parent.”

7. What does the @staticmethod decorator do?

Answer: It defines a method that receives neither self nor cls — it’s just a plain function living in the class namespace.

The three method kinds form a spectrum:

  • Instance method — receives self, the calling instance.
  • Class method (@classmethod) — receives cls, the class.
  • Static method (@staticmethod) — receives nothing automatic. It’s a regular function that happens to be defined inside a class, called as MyClass.method(...) or instance.method(...).

Since it gets no instance and no class, a static method cannot access instance state or class state. It’s used for utility functions that are conceptually related to the class but need no access to it — validation helpers, conversions, factory-adjacent logic that doesn’t need the class.

The interview answer: @staticmethod makes a method that takes no automatic first argument; it behaves like a plain function namespaced inside the class.

Answer:

It defines a method that receives neither self nor cls — it’s just a plain function living in the class namespace.

The three method kinds form a spectrum:

  • Instance method — receives self, the calling instance.
  • Class method (@classmethod) — receives cls, the class.
  • Static method (@staticmethod) — receives nothing automatic. It’s a regular function that happens to be defined inside a class, called as MyClass.method(...) or instance.method(...).

Since it gets no instance and no class, a static method cannot access instance state or class state. It’s used for utility functions that are conceptually related to the class but need no access to it — validation helpers, conversions, factory-adjacent logic that doesn’t need the class.

The interview answer: @staticmethod makes a method that takes no automatic first argument; it behaves like a plain function namespaced inside the class.

8. What does the pass keyword accomplish inside a class definition?

Answer: It’s a syntactic placeholder for an empty body.

A class statement requires an indented block after it. If you want a class with no methods or attributes yet — a stub — you need something in the block, and pass fills it while doing nothing.

class Stub:
    pass

Execution continues normally; the class is created empty. This is common during incremental design, or to define an exception class that needs no added behavior: class MyError(Exception): pass.

pass is purely a no-op statement — it consumes a syntactic slot and performs zero actions. The interview answer: a placeholder so an empty class body is syntactically valid.

Answer:

It’s a syntactic placeholder for an empty body.

A class statement requires an indented block after it. If you want a class with no methods or attributes yet — a stub — you need something in the block, and pass fills it while doing nothing.

class Stub:
    pass

Execution continues normally; the class is created empty. This is common during incremental design, or to define an exception class that needs no added behavior: class MyError(Exception): pass.

pass is purely a no-op statement — it consumes a syntactic slot and performs zero actions. The interview answer: a placeholder so an empty class body is syntactically valid.

My Private Notes

Notes are auto-saved locally to this device.