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
Code that works with a parent object must keep working when you pass a child object. If a child needs special treatment or removes parent behaviour, inheritance is the wrong tool.
Lesson: Has-a versus Is-a: Why Prefer CompositionThe whole object receives a call and passes it to a part to do the real work, as Car.start calls engine.start. It gives you the reuse people often want from inheritance without the tight link.
Lesson: Has-a versus Is-a: Why Prefer CompositionYes, and most real classes do. A Manager can inherit from Employee for the shared interface and also hold a list of reports and an Address as parts. Use each where its sentence is true.
Lesson: Has-a versus Is-a: Why Prefer CompositionIt means an object receives its collaborators from outside, usually through __init__, instead of creating them. You do not need a framework: passing arguments is dependency injection. It makes the class easier to reuse and to test.
Lesson: Objects Working Together: Collaborators and Passing Them InPass the sender in, then give the test a fake that records calls. The test checks the recorded calls and never touches the network. Python also has unittest.mock for the same job, but a small fake class is often clearer.
Lesson: Objects Working Together: Collaborators and Passing Them InUsually the whole. Order keeps its line items private and exposes add and total, so the rules stay in one place. If callers edit the list directly, any rule you add later can be bypassed.
Lesson: Objects Working Together: Collaborators and Passing Them InThe benefit is one source of truth: a deposit is visible to everyone. The risk is surprise changes: code that expected its own copy sees the balance move. Share on purpose, and copy when you need isolation.
Lesson: Objects Working Together: Collaborators and Passing Them InThe default list is created once when the function is defined, so every object that uses the default shares it. Use None as the default and create a new list inside __init__.
Lesson: Objects Working Together: Collaborators and Passing Them InThe caller depends on one method, not on how the object stores its data. You can change the internal structure later without touching callers, and the rules live next to the data they use.
Lesson: Objects Working Together: Collaborators and Passing Them InWithdraw first so a failure stops everything before money is added. In real systems both steps also run inside one database transaction so a crash between them rolls both back, and the operation is locked so two transfers cannot overspend one account.
Lesson: Objects Working Together: Collaborators and Passing Them InA mixin adds one small behaviour, usually has no __init__ or state, and is not meant to be instantiated alone. A normal base class defines a category of objects ("is a kind of"). Python has no special syntax; the name and the intent make it a mixin.
Lesson: Mixins and When Not to Use InheritancePython resolves methods along the MRO, which goes left to right. A mixin that wraps a method has to be found before the main class, then call super() to continue. If it comes last, the main class answers first and the mixin never runs.
Lesson: Mixins and When Not to Use InheritanceWhen the feature has its own state, needs to be swapped or faked in tests, or is shared by several objects at once. A mixin gives every object its own copy of the behaviour and ties it to the class at definition time.
Lesson: Mixins and When Not to Use InheritanceName clashes between mixins, behaviour that depends on attributes the host class must provide, and a complex MRO that is hard to trace. Keep each mixin tiny, document what it expects, and avoid giving mixins their own __init__ arguments.
Lesson: Mixins and When Not to Use InheritanceThe child must disable inherited methods; the only reason to inherit is code reuse; the hierarchy keeps adding a level for each small variation. In those cases use a part, a mixin for one behaviour, or a plain function.
Lesson: Mixins and When Not to Use Inheritancesuper() does not mean "the parent". It means the next class after the current one in the MRO of the instance. That is why cooperating mixins can each call super().method() and every class in the chain runs once.
Lesson: Mixins and When Not to Use InheritanceAn order item is not a kind of product; it refers to a product and adds a quantity. Use composition: OrderItem holds a product and a quantity, and asks the product for its price. That also lets the product price change without breaking the order history design.
Lesson: Composition and Relationships CheckpointUse a mixin for a small stateless behaviour that many classes need and that never changes at run time. Use a part when the feature has its own state, must be swapped or faked, or is shared by several objects.
Lesson: Composition and Relationships CheckpointThe test can pass a fake that records calls, so no email is sent and no database is touched. The class under test is the only real code running, which makes failures easy to locate.
Lesson: Composition and Relationships CheckpointA child must work anywhere its parent works. A Penguin that raises an error in fly() breaks code written for Bird. If a child cannot honour the parent contract, do not inherit.
Lesson: Composition and Relationships CheckpointStart with has-a: hold a part and call it. Reach for inheritance only when you can honestly say "is a kind of" and callers can use the child anywhere the parent works. Composition is easier to change because parts can be swapped, faked and reused.
Lesson: Composition and Relationships CheckpointThe official recommendation is that __repr__ should, if at all possible, look like a valid Python expression that could be passed to eval() to reconstruct an object with the same value (e.g. eval(repr(obj)) == obj). When not feasible, it should be enclosed in angle brackets describing the class and key identifiers (e.g. <User id=12 username="aarav">).
__format__(self, format_spec) is called. If a class does not implement __format__, Python falls back to __str__ if format_spec is empty, or raises TypeError: unsupported format string passed to ... if a specifier was supplied.
Because containers need unambiguous display to distinguish types and boundaries. For example, ["5", 5] clearly distinguishes a string from an integer because repr() adds quotes around "5". If __str__ were used, both would render as 5, producing confusing output like [5, 5].
__repr__ and skip __str__?Most of the time. __str__ falls back to __repr__, so one clear __repr__ covers logs, the shell and print. Add __str__ only when end users need a friendlier text than the developer view, for example an invoice line.
__repr__ returns something that is not a string?Python raises TypeError: __repr__ returned non-string. A common cause is calling print() inside the method instead of returning the text, which returns None.
Leave the sensitive field out of __repr__ (and __str__). With a dataclass, declare it as field(repr=False). Objects get printed in tracebacks and logs, so a secret in __repr__ leaks easily.
f"{x!r}", f"{x!s}" and f"{x}"?!r forces repr(x), !s forces str(x), and a plain {x} calls format(x, ""), which uses __str__ by default. Use !r in error messages and logs so values show their type clearly.
Hash collisions are expected and handled gracefully by Python. When a collision occurs, Python’s hash table implementation uses open addressing with a perturbation recurrence to find the next candidate bucket, checking __eq__ to verify whether the keys are genuinely equal.
Before Python 3.3, string hashes were deterministic across process restarts. Attackers could craft inputs that all collided into the same hash table bucket, degrading O(1) lookups to O(N) and causing Denial of Service (Hash-DoS attacks). Python now salts string and bytes hashes with a random secret per process.
Lesson: Equality and Hashing: __eq__ and __hash__Because Python’s built-in list class is mutable and defines __hash__ = None. If lists could be keys, modifying a list via .append() would corrupt the dictionary’s internal hash table indexing.
The inherited identity hash would break the rule that equal objects share a hash. Python makes the class unhashable so you decide: define a __hash__ from the same fields, or leave it unhashable if the object is mutable.
Yes. This is a hash collision and it is allowed. The set or dict then compares with __eq__ to tell the objects apart. The reverse is not allowed: equal objects must have equal hashes.
The subclass also gets __hash__ = None, so instances become unhashable. To keep hashing, define __hash__ in the subclass, or write __hash__ = Parent.__hash__ when the equality rule is unchanged.
It computes hash(item), jumps to that slot, and compares candidates with ==. On average this takes constant time, which is why a set beats scanning a list for large data, but only when __hash__ and __eq__ agree.
The Python Data Model is the API that defines how objects interact with language syntax (arithmetic operators, indexing, iteration, context managers). By implementing standardized dunder (double-underscore) methods like __len__ or __iter__, custom objects integrate seamlessly with Python’s built-in operators and standard functions.
When a user writes obj[1:5:2], Python passes a slice object (slice(1, 5, 2)) to __getitem__(self, key). The method can inspect isinstance(key, slice) and use key.indices(len(self)) to calculate valid start, stop, and stride boundaries.
An Iterable is an object with an __iter__() method that returns an iterator (e.g. lists, dicts, custom sequences). An Iterator is an object with both __iter__() (returning self) and __next__() which returns the next item or raises StopIteration when exhausted.
An iterable has __iter__ and can produce an iterator, for example a list. An iterator has __next__ and remembers its position, and it is used up once finished. A for loop calls iter() on the iterable, then next() on the iterator.
__iter__ usually better than returning self with __next__?A generator method creates a new iterator on every call, so the object can be looped over many times and independent loops do not interfere. Returning self shares one position, so a second loop sees nothing.
item in obj work when a class defines only __iter__?Python loops through the object and compares each item with ==, stopping at the first match. It is a linear scan. Define __contains__ when a faster check is possible, such as a lookup in a set or dict.
__bool__ and __len__ exist, which one decides truthiness?__bool__ wins. Without it, Python uses __len__, and length 0 means falsy. Define __bool__ when emptiness is not about size, for example an account that is closed.
namedtuple creates tuple subclasses: they are always immutable, iterable by position, unpackable like tuples, and have low memory overhead. dataclass creates regular mutable (unless frozen) classes, supports inheritance, default factories, __post_init__, and cleaner syntax without tuple indexing confusion.
__post_init__(self) runs immediately after the auto-generated __init__ completes. It is used to perform validation (e.g. checking value ranges), compute derived attributes from input parameters, or initialize non-field states.
Yes! In Python 3.10+, you can pass @dataclass(slots=True). This automatically generates __slots__ from the annotated fields, eliminating __dict__ and reducing memory footprint significantly.
No. By default eq=True and frozen=False, so Python sets __hash__ to None and the object is unhashable. Use frozen=True (hash from fields) or unsafe_hash=True if you are sure the fields never change.
A dataclass gives named, typed fields, methods, defaults, validation in __post_init__ and optional freezing. A namedtuple is immutable and tuple-like. A dict has no fixed shape, so typos in keys appear only at run time.
Declare it with field(repr=False) or field(compare=False). Use repr=False for secrets such as tokens, and compare=False for data like a cache that should not affect equality.
No, it is shallow. The fields cannot be reassigned, but a mutable object inside, such as a list, can still change, and then the hash may fail or differ. Use tuples for collections inside frozen objects.
Lesson: Modern Dataclasses for Domain ModelsEach lesson has quizzes, practice and more interview questions on the same topic.
Open the first lesson