Deep Engineering

Python · Interview preparation

Python interview questions: short answers

The short answer from every lesson in the track — the lesson’s own text, minus the “Short answer:” label — and the questions from its “Next they ask” section. The answers to those questions, the mechanism and the measurements behind it all are in the linked lesson.

  1. 01

    Classes and OOP

    A class holds the attributes and methods shared by its instances, an instance of an ordinary class holds its own data in its own dictionary. A method is a function in the class, self is its first argument: a.m() is equivalent to A.m(a). Reading an ordinary attribute looks in the instance, then in the class; assignment through an instance writes to the instance. @classmethod receives the class, @staticmethod receives nothing.

    Full answer in the lesson →

    Next they ask

  2. 02

    Built-in collections

    A list is an ordered mutable collection with repeats, a tuple is immutable, a dictionary maps key → value with unique keys, a set holds unique elements without order. Membership checks and lookups by value are done on a set or a dictionary: there it is a hash lookup — O(1) on average and O(n) in the worst case, when hashes coincide — in a list it is a scan. Only a hashable object can be a set element or a dictionary key; dictionary values can be anything. A dictionary keeps insertion order, a set does not.

    Full answer in the lesson →

    Next they ask

  3. 03

    Modules and imports

    import finds a module and binds a name. The module body runs on the first successful load; a repeated import under the same full name in the same interpreter takes the module from the sys.modules dictionary. from m import x copies a reference to the value at the moment of import — a new assignment to m.x does not change it. if __name__ == "__main__" separates launch code from code that will run on import.

    Full answer in the lesson →

    Next they ask

  4. 04

    Complexity of operations

    O-notation describes how the work grows with the input; strictly it is an upper bound. x in list, insert(0, x), remove are O(n); x in set, d[key] are O(1) on average and O(n) in the worst case, when hashes coincide; append is amortized O(1); bisect is O(log n); sorted() is O(n log n) in the average and worst case. The complexity of code is the number of iterations times the cost of the operation inside.

    Full answer in the lesson →

    Next they ask

  5. 05

    Strings and formatting

    A string is immutable — methods and += return a result, and the original string does not change. Slices s[i:j:k] do not include the right bound, negative indices count from the end, slice bounds are clipped to the string's length — so a slice entirely beyond it is empty. Format with f-strings with a specification after the colon. A string is built from many pieces with "".join(...): the documentation calls repeated concatenation quadratic, and CPython's fast path is a conditional optimization. str is Unicode code points, bytes is bytes, between them — encode and decode.

    Full answer in the lesson →

    Next they ask

  6. 06

    Testing

    A test calls code and checks the result; in unittest FAIL is a check that fired (AssertionError), ERROR is any other exception. External dependencies are replaced with Mock via patch — in the module where the name is used. The main claim of a test is about the result of the code; checking the mock's call is an additional one. Mocks are built from the real object: Mock(spec=…) rejects non-existent names, while a wrong call is caught only by create_autospec or autospec=True. Tests are independent: they do not rely on the order in which they run, state is prepared in setUp or a fixture.

    Full answer in the lesson →

    Next they ask

  7. 07

    Threads and processes

    Threads share the memory of a process, processes are isolated. In regular CPython — the build with the GIL — threads run Python code in turn, so for waiting — network, disk — you take threads or asyncio, and for computation in Python — processes; C extensions can release the GIL themselves. Tasks are passed to processes via pickle, starting processes is more expensive, and a process pool is created in main() under if __name__ == "__main__":. The GIL does not prevent races: shared state is protected with a Lock.

    Full answer in the lesson →

    Next they ask

  8. 08

    Environments & dependencies

    A virtual environment is a folder with its own interpreter and its own site-packages; it is created with python -m venv. The environment's interpreter finds it by itself from pyvenv.cfg, and activate is for the shell — through PATH the python command finds the environment's interpreter. Packages are installed with python -m pip inside the environment, the installed versions are recorded with pip freeze in requirements.txt — a snapshot of what is installed, not a lock file. Packages are not installed into the system Python: an environment isolates the versions of projects.

    Full answer in the lesson →

    Next they ask

  9. 09

    Decorators

    @d above def f is f = d(f), run once, at the moment the interpreter reaches def. With several decorators the expressions after @ are evaluated top-down and applied bottom-up. functools.wraps makes the wrapper look like the target rather than behave like it: inspect reports the target's signature only because it walks __wrapped__, while the wrapper itself still accepts *args, **kwargs.

    Full answer in the lesson →

    Next they ask

  10. 10

    Descriptors

    A descriptor is an object that lives on the class and intervenes in attribute access on an instance; the single criterion is that its type has __get__, __set__ or __delete__. All the behaviour follows from one line of precedence: a data descriptor (one with __set__ or __delete__) outranks the instance dictionary, a non-data descriptor (only __get__) is outranked by it. property, an ordinary method, staticmethod, classmethod, a slot and cached_property already are descriptors.

    Full answer in the lesson →

    Next they ask

  11. 11

    Iterators and generators

    An iterable can hand you an iterator (__iter__); an iterator can hand you the next item (__next__) and must hand back itself from __iter__. One method of difference, and one difference in behaviour: a list can be walked twice, an iterator cannot. A generator is an iterator written as a function, and calling a generator function does not run its body — it builds an object and returns.

    Full answer in the lesson →

    Next they ask

  12. 12

    Context managers

    A with is a guarantee that leaving the block will be handled, however the block ends. The protocol is two methods on the type: __enter__ is called on the way in, __exit__ on the way out, and as binds what __enter__ returned rather than the object itself.

    Full answer in the lesson →

    Next they ask

  13. 13

    Exceptions and finally

    try is the attempt, except handles the error, else is the "no error happened" branch, finally is the exit taken either way. And the key point about finally: if it transfers control outwards itself — return, break, continue — the exception in flight disappears. The reference puts it in one sentence: "the saved exception is discarded".

    Full answer in the lesson →

    Next they ask

  14. 14

    lambda

    A lambda is the same function written as an expression. The object it builds is of the same type as a def's, with the same bytecode for the body; you write one where the function is needed right at the call site and a separate name would hurt readability.

    Full answer in the lesson →

    Next they ask

  15. 15

    async/await

    Calling a coroutine function (async def without yield) does not run its body — it only creates a coroutine object. The body is run by whatever drives that object: await in the current task, asyncio.run, or a task from create_task, and a task may start working before anyone writes await task. await yields control to other tasks only if what it awaits is not ready yet: awaiting a finished Future hands over no turn. Hence the forgotten await: if is_allowed(user) tests the coroutine object for truth instead of the result of the check, and so passes for any data.

    Full answer in the lesson →

    Next they ask

  16. 16

    *args and **kwargs

    A star in a declaration collects, a star in a call spreads. One star collects the surplus positional arguments into the tuple args, two collect the surplus keyword arguments into the dict kwargs; in a call, one spreads a sequence into positional arguments and two spread a dict into keyword arguments. Both objects are built even when there is nothing to pass: you get () and {}, not None.

    Full answer in the lesson →

    Next they ask

  17. 17

    List vs tuple

    A list is a mutable sequence and a tuple an immutable one, and that — not bytes — is what you choose between. A tuple can be a dictionary key whenever every element of it can, and it tells the reader "this is one record of a fixed shape"; a list says "this is any number of uniform things". There is a difference in memory, but it is not what you would change the type for.

    Full answer in the lesson →

    Next they ask

  18. 18

    Comprehensions

    A comprehension is a loop written as a single expression, but it runs in a scope of its own, and the boundary of that scope is inside the square brackets rather than around them. Everything except the iterable expression in the leftmost for runs in a separate, implicitly nested scope — which is why the comprehension's variable does not leak out, and why the comprehension does not always see the same names as the code around it.

    Full answer in the lesson →

    Next they ask

  19. 19

    Closures

    A closure is a function that kept access to the variables of the place it was created in, and goes on using them after that place has finished running. What it kept is access to the variable, not a copy of its value: the value is read at call time. That is why a change made to the variable after the function was created reaches it.

    Full answer in the lesson →

    Next they ask

  20. 20

    MRO

    The MRO is the order in which Python looks an attribute up across the classes of a hierarchy; it lives in Class.__mro__ and is built by the C3 algorithm. C3 puts a class before its ancestors and keeps the order of the bases, so in the diamond D(Left, Right) the common ancestor goes to the tail: D, Left, Right, Base, object. And super() is a proxy: lookup through it goes along the instance's MRO, starting after the class where the method is written, and that is not necessarily the parent.

    Full answer in the lesson →

    Next they ask

  21. 21

    Copying

    Assignment copies nothing — it is a second name for the same object; copy.copy builds a new top-level object and leaves the nested ones shared; copy.deepcopy reaches the nested ones too. The difference shows only when you change something nested: after a shallow copy the change is visible from both sides, after a deep one it is not.

    Full answer in the lesson →

    Next they ask

  22. 22

    is versus ==

    is asks whether this is the same object; it cannot be overridden, and its cost does not depend on the object. == calls __eq__, an ordinary method that may return anything. is belongs where the API contract is identity itself: with any singleton object and any sentinel created for such a comparison — None, NotImplemented, Ellipsis, Enum members, your own object().

    Full answer in the lesson →

    Next they ask

  23. 23

    Garbage collection

    In CPython memory is freed first of all by reference counting: an object dies as soon as its last reference disappears, at once. del deletes a name, not an object. The garbage collector supplements the count and is needed for cycles: objects that refer to each other would never be freed by the count, and gc finds such groups with no outside references.

    Full answer in the lesson →

    Next they ask

  24. 24

    Metaclasses

    A metaclass is the type of a class. A class is an instance of its metaclass exactly as an object is an instance of its class, and a metaclass governs how classes are created in much the same way a class governs how instances are created. Hence Class(...) calling the metaclass's __call__: the class is its instance.

    Full answer in the lesson →

    Next they ask

  25. 25

    Default arguments

    The expression after = is evaluated once, when the def executes, and the resulting object is handed to every call in which that argument is not passed. That is why a mutable default accumulates changes between calls. The fix is a None check; a sentinel of your own is needed only where None is a legitimate argument value.

    Full answer in the lesson →

    Next they ask

← Track map and final check