Loading...
Loading...
Python interview guide
Classes, inheritance, dunder methods, properties and dataclasses. Every answer is short enough to say aloud, and every question links to the lesson that teaches it.
49 questionsLast updated
Explicit self adheres to the Zen of Python: "Explicit is better than implicit." It eliminates ambiguity between local variables inside the method body and instance attributes, simplifies the syntax for decorator wrapping, and avoids dynamic scoping surprises.
obj.__class__ is a reference to the class object from which obj was instantiated. It enables dynamic type inspection and reflection at runtime.
A function defined inside a class is a normal function object (<class 'function'>). When accessed via an instance (obj.method), Python’s descriptor protocol binds the function to the instance, returning a bound method object (<class 'method'>) that automatically supplies obj as the first argument.
It runs the indented body once, collects the names defined there into a namespace, builds a class object from them, and binds the class name to that object. Nothing is an instance yet; instances appear only when the class is called.
Lesson: Classes, Instances and the self ParameterYes. A class is an instance of type, so type(Car) is type. You can pass it to functions, store it in a list or return it, which is what makes factories and registries easy.
On the class. Each instance keeps only its own data in its dictionary, so a million objects share one copy of the code. Attribute lookup finds the method on the class and binds it to the instance when you call it.
Lesson: Classes, Instances and the self ParameterCar.drive(my_car) valid, and when would you write it?It is exactly what my_car.drive() expands to. You rarely write it, but it appears when calling a parent version of a method explicitly or when passing the unbound function as a callback.
__new__(cls, ...) is the static constructor method responsible for creating and returning a new instance of the class in memory. __init__(self, ...) is the initializer that receives the newly created instance and sets up its attributes. __new__ is overridden when subclassing immutable types (like int or tuple) or implementing singletons.
Yes. Since __init__ is a regular method, calling obj.__init__(...) will execute its body and re-initialize the instance attributes. However, doing so mutates existing state and is rarely good practice.
Constructors should be fast and lightweight. Placing blocking network calls, database queries, or heavy file I/O inside __init__ violates separation of concerns, makes unit testing difficult, prevents asynchronous initialization, and can leave half-initialized orphan objects if an exception is thrown.
Not by overloading: a class has one __init__. You get the same effect with default and keyword arguments, or with classmethod factories such as from_string, each of which ends by calling cls(...).
__init__ raises an exception?The exception goes to the caller and the half-built object is discarded when no name refers to it. That is why validation belongs in __init__: callers never receive an invalid object.
__init__?Because a reader cannot see the full shape of the object in one place, and calling a method early may hit a missing attribute. Set every attribute in __init__, even if the first value is None.
Storing the argument keeps a reference to the caller's list, so later changes by the caller silently change your object. Copying with list(items) gives the object its own data, at the cost of one copy.
obj.method(x) into a call that passes obj as self?Functions defined in a class are descriptors. When you look up obj.method, Python finds the function on the class and its __get__ returns a bound method that stores obj and the function. Calling that bound method calls the function with obj placed in front of x.
A plain function gets only what you pass. An instance method is defined inside a class and gets the object as its first parameter, so it can read and change that object's attributes. Use a method when the work belongs to the object's data; otherwise a plain function is simpler.
Lesson: Instance Methods and selfYes: Account.deposit(acct, 100) does the same as acct.deposit(100), because you supply the object yourself. It is rare in normal code, but it is how a subclass calls a parent's version of a method, for example Parent.describe(self).
Prefer one job per method. A method that changes state usually returns nothing, and a method that answers a question usually changes nothing. Mixing the two makes calls surprising; list.sort() returning None is a built-in example of keeping them apart.
acct.deposit == acct.deposit True but acct.deposit is acct.deposit False?Each time you write acct.deposit, Python builds a new bound method object, so they are different objects. Two bound methods compare equal when they wrap the same function and the same object, which is the case here.
No. The function is stored once on the class. Objects hold only their own data, and the method finds that data through self. That is why thousands of objects cost no extra method code.
If the code needs the data of one object, use an instance method. If it needs the class itself, for example to build objects in another way, use a class method. If it needs neither, it is a plain function and may be a static method. The details are in the Class and Static Methods module (opens in a new tab).
Lesson: Instance Methods and selfBy default, Python stores instance attributes in a dynamic dictionary __dict__, which incurs significant memory overhead. Defining __slots__ = ('attr1', 'attr2') suppresses __dict__ and allocates a fixed-size array of attribute references, saving up to 40-50% memory for classes with millions of instances.
If class Child(Parent) does not define class_var, looking up Child.class_var searches up the MRO to Parent.class_var. Assigning Child.class_var = 50 creates a new class variable on Child, leaving Parent.class_var intact.
In Java/C++, static fields are statically bound to the class type. In Python, class variables are dynamic attributes on first-class class objects living on the heap. They can be created, rebound, or deleted dynamically at runtime using setattr(cls, name, val) or delattr.
When sharing is the purpose: a registry of created objects, a cache, or a counter. Document it, give it a clear name, and remember it also shares state between tests and threads, so reset or guard it where that matters.
Lesson: Class Variables vs Instance Variablesself.count += 1 when count is a class variable?Python reads self.count, which finds the class value, adds 1, and then assigns the result to self.count, creating an instance attribute. The class variable is unchanged. To change the class value, write ClassName.count += 1 or use a classmethod.
No. An uppercase name such as MAX_RETRIES is only a convention, and typing.Final is checked by type checkers, not at run time. Anyone can still rebind it, so treat it as read-only by agreement.
Check "name" in obj.__dict__ or vars(obj) for the instance, and "name" in type(obj).__dict__ for the class. If the name is in both, the instance value wins when you read obj.name.
Python prioritizes pragmatism, introspection, and flexibility. Hard access controls complicate debugging, testing, serialization, and dynamic proxying (e.g. mock frameworks). By using conventions like _protected, Python gives developers full power while clearly communicating developer intent.
If a parent class defines an internal method __setup() and a subclass also defines __setup(), without mangling the subclass method would accidentally override the parent’s private initialization routine. With mangling, they become _Parent__setup() and _Child__setup(), keeping both intact.
Yes. When a module uses from module import *, any top-level names prefixed with a single leading underscore are excluded from the wildcard import, preventing internal helpers from polluting caller namespaces.
Rarely. I use __name only in a class meant to be subclassed, where a subclass could pick the same attribute name and break the parent by accident. For everything else a single underscore says "internal" clearly enough, and it is easier to test and debug.
Show that obj._ClassName__attr reads it. Mangling renames the attribute, nothing more. Real secrets belong in environment variables or a secrets store, and are never kept as plain object state that is printed or logged.
Because @property can add validation later without changing how callers write obj.x. A plain attribute costs nothing now, and the change to a property stays invisible to callers. Writing get_x and set_x up front adds code that gives no benefit.
property is a built-in class implementing __get__, __set__, and __delete__. When accessed on an instance, property.__get__ calls the registered getter method. When assigned, property.__set__ calls the registered setter method. Because it defines __set__, it is a data descriptor, which always takes precedence over instance __dict__ entries.
A plain attribute lookup accesses __dict__ directly in O(1) time in C. A property lookup incurs function call overhead (Python stack frame creation and descriptor dispatch). For standard business applications the overhead is negligible, but in tight numerical loops cached attributes or raw floats may be preferred.
Yes. A subclass inherits the parent property descriptor. To override only the setter while keeping the parent getter, the subclass can write @ParentClass.property_name.setter.
Replace the attribute with a property of the same name: the getter returns self._name and the setter validates and stores it. Callers keep writing obj.name, so none of the 40 files change. Make __init__ assign through the property so the first value is checked too.
Writing self.price = value inside the price setter calls the setter again, so it recurses until Python raises RecursionError. The setter must store the data under a different name, usually self._price.
Use a property when the value feels like data: cheap, no side effects, same result for the same state. Use a method with a verb name when it does real work such as a network call, a file read or a change of state, so the cost is visible at the call site.
Lesson: The @property Decorator and SettersProperty setters are ideal for domain models in pure Python standard library applications where you want fine-grained encapsulation with zero external dependencies. For API payloads, serialization, and complex nested JSON validation, libraries like Pydantic provide declarative type hints, automated parsing, and performance-optimized validation in Rust/C.
Lesson: Validation and Data Integrity with PropertiesYes. For invariants like min_age <= max_age, a setter can check the relationship against the sibling property (e.g. if value > self._max_age: raise ValueError()). For complex transactional updates, a dedicated method like set_age_range(min_age, max_age) is often cleaner than individual setters.
LBYL ("Look Before You Leap") tests conditions upfront with explicit if not isinstance(...) checks. EAFP ("Easier to Ask for Forgiveness than Permission") attempts conversion directly in a try...except (ValueError, TypeError) block. Python encourages EAFP for duck typing while maintaining explicit checks where bounds matter.
In Python True and False are instances of int, with values 1 and 0. A plain isinstance(v, int) lets True through as a quantity of 1. For counts and ids I reject bool first, then check int.
__init__ stores self._score directly instead of assigning self.score?The first value skips the setter, so a new object can start in an invalid state while later changes are checked. Assigning through the property makes creation and updates follow one rule and keeps the validation code in a single place.
Lesson: Validation and Data Integrity with PropertiesRaise. Assignment has no return value to inspect, so an error code would be silently ignored. An exception stops the bad value from being stored and tells the caller what was wrong, with the field name and the bad value in the message.
Lesson: Validation and Data Integrity with PropertiesThe Principle of Least Privilege states that callers should only be granted access to the minimum information and capabilities necessary to perform their work. In Python, this means exposing high-level intent methods (like submit_order()) while keeping internal states, IDs, and raw helper routines protected.
In pytest, use the pytest.raises context manager: ``python with pytest.raises(ValueError, match="cannot be negative"): account.balance = -10 `` This asserts both the exception type and the error message.
Because attribute syntax (obj.attr) implies a lightweight, idempotent, side-effect-free field access. If a property performs a slow database query or alters object state, callers will be surprised by poor performance and unpredictable side-effects.
Each lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson