Common Python technical interview questions spanning beginner to advanced, covering core language features, data structures, OOP, concurrency, and performance.
Python is dynamically typed, interpreted, and emphasizes readability via indentation-based syntax. It supports multiple paradigms (OOP, functional, procedural), has a vast standard library, and uses automatic memory management via garbage collection.
Lists are mutable (elements can be changed after creation) while tuples are immutable. Tuples are generally faster, can be used as dictionary keys, and are preferred for heterogeneous fixed data; lists are preferred for homogeneous collections that need modification.
Immutable types cannot be changed after creation: int, float, str, tuple, frozenset, bytes. Mutable types can be modified in place: list, dict, set, bytearray. Mutability affects how objects behave when passed to functions and used as dictionary keys.
Python uses reference counting as its primary memory management mechanism; when an object's reference count drops to zero it is deallocated. A cyclic garbage collector handles reference cycles that reference counting alone cannot resolve.
'==' compares values (equality), while 'is' compares object identity (whether two variables point to the exact same object in memory). For example, two separate lists with identical contents are '==' but not 'is'.
*args collects extra positional arguments into a tuple, and **kwargs collects extra keyword arguments into a dictionary. They are used to write functions that accept a variable number of arguments, enabling flexible APIs and decorator patterns.
A decorator is a callable that wraps another function to extend or modify its behavior without changing its source code. It is applied with the '@decorator' syntax and works by accepting a function, returning a new function that typically calls the original internally.
List comprehensions ([x for x in iterable]) eagerly evaluate and store the full result in memory. Generator expressions ((x for x in iterable)) are lazy — they yield items one at a time, making them memory-efficient for large or infinite sequences.
The GIL is a mutex in CPython that allows only one thread to execute Python bytecode at a time, preventing true parallel execution of CPU-bound threads. For CPU-bound tasks the multiprocessing module bypasses the GIL; for I/O-bound tasks threading or asyncio work well despite it.
A shallow copy (copy.copy()) creates a new object but references the same nested objects as the original. A deep copy (copy.deepcopy()) recursively copies all nested objects, so modifications to the copy do not affect the original at any level.
The 'with' statement calls __enter__ on entry and guarantees __exit__ is called on exit, even if an exception occurs. It is used for resource management (files, locks, DB connections) and can be created via a class implementing __enter__/__exit__ or the contextlib.contextmanager decorator.
A generator is a function that uses 'yield' to produce a sequence of values lazily, suspending execution between calls. Each call to next() resumes from where it left off, making generators memory-efficient for pipelines or large datasets.
MRO defines the order in which Python searches classes for a method in an inheritance hierarchy, determined by the C3 linearization algorithm. super() follows the MRO to call the next class's method, enabling cooperative multiple inheritance without hardcoding class names.
@staticmethod defines a method that receives no implicit first argument — it behaves like a plain function scoped to the class. @classmethod receives the class itself as the first argument (cls) and is commonly used for factory methods or accessing/modifying class-level state.
__slots__ replaces the per-instance __dict__ with a fixed set of descriptors, reducing memory usage and slightly speeding up attribute access. Use them when creating many instances of a class with a known fixed set of attributes and memory efficiency matters.
asyncio's event loop runs coroutines cooperatively — when a coroutine hits 'await', it suspends and yields control back to the loop, allowing other coroutines to run. async/await enables non-blocking I/O-bound concurrency in a single thread without the complexity of callbacks.
A metaclass is the 'class of a class' — it controls how classes themselves are created, allowing you to modify class attributes, enforce interfaces, or register classes automatically. You use them for frameworks, ORMs, or enforcing coding contracts at class-definition time.
A descriptor is an object that defines __get__, __set__, or __delete__, which Python calls when the attribute is accessed on a class or instance. Properties, classmethod, staticmethod, and ORM field types all leverage the descriptor protocol under the hood.
Use threading for I/O-bound tasks where the GIL is less limiting; use multiprocessing for CPU-bound tasks to achieve true parallelism across cores; use asyncio for high-concurrency I/O-bound tasks (e.g., thousands of network connections) with minimal overhead and a single-threaded model.
Start with cProfile or the timeit module to identify bottlenecks, then examine algorithmic complexity before micro-optimizing. Common optimizations include using built-in functions, replacing loops with NumPy vectorization, caching with functools.lru_cache, and moving hot paths to C extensions or Cython.
© RM Full Stack & AI Engineer · All interview questions · Roadmaps · Open the app