Python Expert
What does the if __name__ == '__main__' idiom do, and why is it used?
Select the correct answer
It runs the guarded code only when the file is imported, never when executed directly
It declares a private namespace so the module's names stay hidden from importers
It marks the file as the program's required entry point before any imports load
It runs the guarded code only when the file is executed directly, not when imported
Is Python a compiled or interpreted language? Explain the role of .pyc files and the Python Virtual Machine (PVM).
Select the correct answer
Source is compiled straight to machine code stored in .pyc files for the PVM
Source is compiled to bytecode cached in .pyc files, then run by the PVM
Source is interpreted line by line and .pyc files hold optimized native binaries
Source is linked into an executable while .pyc files store the program's runtime state
What are the major differences between Python 2 and Python 3 that you should be aware of?
Select the correct answer
In Python 3 print is a function, strings are Unicode, and / does true division
In Python 3 range returns a list, input evaluates code, and tabs are required
In Python 3 print is a statement, strings are bytes, and / does floor division
In Python 3 xrange replaces range, and integers silently overflow to floats
What happens at the bytecode level when a Python script is executed?
Select the correct answer
Source is compiled to bytecode that the PVM executes instruction by instruction
Source is translated to assembly that the CPU executes register by register
Source is parsed into a syntax tree that the OS executes node by node
Source is converted to C code that a compiler executes function by function
How does the 'Specializing Adaptive Interpreter' introduced in 3.11/3.12 improve performance?
Select the correct answer
It caches function return values so repeated calls skip executing the bytecode
It compiles the entire module into native machine code ahead of program startup
It rewrites hot bytecodes into type-specialized versions based on observed runtime types
It removes the GIL so specialized threads can run bytecodes fully in parallel
What is the fundamental difference between the is operator and the == operator in terms of memory and the data model?
Select the correct answer
is compares object identity (same memory), while == compares values
is compares type and hash, while == compares the underlying memory layout
is copies then compares, while == references then compares identity directly
is compares the values held, while == compares identity (same memory address)
Explain the LEGB rule. What happens to the scope when you use the global or nonlocal keywords inside a nested function?
Select the correct answer
Names resolve Local, Enclosing, Global, Built-in; global rebinds the enclosing scope, nonlocal rebinds module level
Names resolve Local, Enclosing, Global, Built-in; global rebinds module scope, nonlocal rebinds the enclosing one
Names resolve Built-in, Global, Enclosing, Local; both global and nonlocal create a brand-new local name
Names resolve Local, Enclosing, Global, Built-in; global and nonlocal both make the variable read-only everywhere
What happens if you use a mutable object like a list or dictionary as a default argument in a function, and why is this considered a gotcha?
Select the correct answer
Each call copies the default, yet the copies all still reference the same underlying buffer in memory.
The default is created once at definition time and shared across calls, so mutations persist between invocations.
The default is rebuilt on every call, but Python caches the first one and silently reuses it for speed.
The interpreter freezes the default as immutable, so any attempt to mutate it raises an error at runtime.
What is a closure in Python, and how does the interpreter 'remember' the values of the enclosing scope even after the outer function has finished executing?
Select the correct answer
A nested function captures enclosing variables in cell objects, which keep those references alive after the outer call.
A function copies all outer local values into its own namespace, freezing them when the outer function is defined.
A decorator stores enclosing variables in a global table, letting the inner function look them up on each call.
A nested function reads enclosing variables from the call stack, which Python preserves until the program exits.
Is Python 'call-by-value' or 'call-by-reference'? Explain the concept of 'call-by-object-reference' (or 'assignment').
Select the correct answer
Python is call-by-reference, so reassigning any parameter inside a function changes the caller's original variable too.
Python passes immutables by value and mutables by reference, choosing the strategy from each argument's type.
Python passes object references by value, so rebinding a parameter is local but mutating a shared object is visible.
Python is call-by-value, copying every argument fully, so functions can never modify the caller's objects at all.
What is string and integer interning in Python, and why does a=256;b=256;a is b return True while a=257;b=257;a is b might return False?
Select the correct answer
Python interns all integers below 1000 by default, so larger numbers like 257 get fresh objects each assignment.
Integers are compared by value with is, so small numbers match but large ones land in distinct slots.
CPython pre-caches integers from -5 to 256, so equal values share one object; 257 lies outside that cached range.
The garbage collector merges duplicate integers up to 256, then stops deduplicating beyond that size threshold.
What is the difference between a list, a tuple, a set, and a dictionary, and when would you choose each?
Select the correct answer
List maps keys to values, tuple is mutable ordered, set is an immutable sequence, dict holds unique unordered items.
List is ordered immutable, tuple is mutable unique, set is a key-value mapping, dict is an ordered mutable list.
List is unordered mutable, tuple is ordered unique, set maps keys to values, dict is an immutable sequence type.
List is an ordered mutable sequence, tuple is ordered immutable, set holds unique items, dict maps keys to values.
What is a namedtuple, and how does it compare to a regular tuple or a class?
Select the correct answer
A dict subclass with fixed keys that acts like a tuple but allows item reassignment later
A mutable tuple variant that allows attribute access and supports adding new fields later
A tuple subclass with named fields giving readable attribute access while staying immutable
A lightweight class with named slots that is mutable and compares by identity not value
How do Python's collections.deque and list differ in terms of time complexity for common operations?
Select the correct answer
deque offers O(1) random indexing, while list needs O(n) to reach any element by its position.
deque gives O(1) appends and pops at both ends, while list is O(1) at the end but O(n) at the front.
list provides O(1) front insertion, while deque requires O(n) to append items at either end.
Both list and deque provide O(1) appends at both ends, but deque uses O(n) memory to resize.
What requirements must an object meet to be used as a key in a Python dictionary, and how does Python handle hash collisions?
Select the correct answer
Keys must be unique objects by identity; collisions are handled by chaining values into nested dictionaries.
Keys must be hashable with consistent __hash__ and __eq__; collisions are resolved by probing for open slots.
Keys must be immutable strings or numbers only; collisions are stored together in a linked list per bucket.
Keys must implement __lt__ for ordering; collisions trigger a full resize and rehash of the entire table.
What is a frozenset, and how does it differ from a regular set?
Select the correct answer
An immutable set that is hashable and can be used as a key in a dictionary
A mutable set whose elements are automatically kept sorted in ascending order
An ordered set that preserves insertion order while rejecting any duplicates
A set that stores frozen copies of its objects to block their later mutation
Are Python dictionaries ordered? Explain the insertion-order guarantee introduced in Python 3.7.
Select the correct answer
No; ordering is arbitrary, so you must use OrderedDict to retain order
No; dicts order keys by hash value, so it depends on the keys that you insert
Yes; dicts have always kept their keys sorted alphabetically since early versions
Yes; since 3.7 dicts preserve insertion order of keys as a language guarantee
What are defaultdict, Counter, and OrderedDict in the collections module, and when would you use them?
Select the correct answer
defaultdict sorts keys, Counter limits duplicate keys, OrderedDict reverses insertion order
defaultdict supplies defaults for missing keys, Counter tallies items, OrderedDict tracks order
defaultdict locks keys, Counter caps values, OrderedDict sorts keys numerically
defaultdict caches lookups, Counter stores unique keys, OrderedDict indexes by position
Beyond mutability, what are the architectural differences between a list and a tuple, and why is a tuple slightly more memory-efficient?
Select the correct answer
Tuples omit pointers and keep values directly, while lists keep both pointers and a separate index cache.
Tuples share a single global instance per length, while lists always allocate a brand-new buffer on creation.
Tuples store elements inline as raw bytes, while lists box every element, doubling their per-item memory cost.
Tuples are fixed-size and allocated once without spare capacity, while lists over-allocate room for future growth.
What is the purpose of the else and finally clauses in a try/except block?
Select the correct answer
else runs when an exception is caught; finally runs only if none was raised
else and finally both run only when a raised exception is handled by except
else runs before the try block starts; finally runs only if an error is unhandled
else runs when no exception occurred; finally always runs for cleanup regardless
Explain the 'Easier to Ask for Forgiveness than Permission' (EAFP) coding style. Why is it often preferred in Python over 'Look Before You Leap' (LBYL)?
Select the correct answer
Try the operation and handle exceptions if it fails, rather than checking conditions first
Check all preconditions with guards before attempting any potentially failing operation
Avoid exceptions entirely by returning error codes that the caller must inspect each time
Validate inputs at function boundaries so no exception can ever be raised at runtime
Why is it considered bad practice to use except Exception? Explain the Python exception hierarchy and where BaseException fits in.
Select the correct answer
It only catches custom errors; BaseException derives from Exception at the bottom
It also catches SystemExit and KeyboardInterrupt; both classes are identical
It hides bugs by catching unrelated errors; BaseException is the root, Exception sits below
It runs slowly at runtime; BaseException is the root but Exception sits above it
What is duck typing, and how does it differ from EAFP?
Select the correct answer
Duck typing checks an object's type with isinstance before calling any method on it
Duck typing judges an object by the methods it supports rather than its explicit type
Duck typing converts objects to one common type automatically before any operation is run
Duck typing requires objects to inherit from a shared base class before being used together
How do you define a custom exception, and what are the best practices for exception hierarchies?
Select the correct answer
Subclass object and raise it directly, since any class instance can be raised here.
Subclass str so the exception carries its message text without any extra attributes.
Subclass Exception and group related custom errors under a shared base exception class.
Subclass BaseException directly so the errors propagate through all interpreter layers.
What is exception chaining, and what does 'raise ... from ...' do?
Select the correct answer
It re-raises the original exception unchanged after running the intervening cleanup code.
It sets __cause__ on the new exception, explicitly linking it to the original cause.
It suppresses the original traceback entirely so only the new exception is ever displayed.
It merges both exceptions into a single object combining their messages and tracebacks.
What is the difference between a classmethod, a staticmethod, and a regular instance method?
Select the correct answer
A staticmethod receives cls, a classmethod receives self, an instance method receives neither.
An instance method receives cls, a classmethod receives self, a staticmethod the module.
An instance method receives self, a classmethod receives cls, a staticmethod receives neither.
All three receive self, but only a classmethod can modify shared class-level state safely.
What is the difference between __str__ and __repr__, and when is each used?
Select the correct answer
__str__ is called by logging only, while __repr__ is used everywhere a string is needed.
__repr__ formats floats and ints, while __str__ handles only container and sequence types.
__str__ gives a readable form for users; __repr__ gives an unambiguous form for developers.
__repr__ gives a readable form for users; __str__ gives an unambiguous form for developers.
What is an Enum in Python, and why would you use one instead of plain constants?
Select the correct answer
A dictionary subclass mapping names to values with no protection against duplicates
A mutable container of constants that can be reassigned at runtime for added flexibility
A subclass of int whose members are plain integers indistinguishable from literals
A class of named constant members that gives readable names, grouping, and type safety
What are the advantages of using @dataclass over a regular class or a namedtuple, and how does it handle default values and mutability?
Select the correct answer
It requires a default factory for every field and forbids inheritance from regular classes.
It enforces runtime type checking on all fields and rejects mismatched default values.
It produces immutable tuples like namedtuple but with faster attribute access by index.
It auto-generates __init__, __repr__, and __eq__ while allowing mutable, typed fields.
How do Python's 'dunder' (magic) methods allow for operator overloading and protocol implementation?
Select the correct answer
Python compiles dunder methods into C-level hooks that bypass normal method resolution order.
Python dispatches operators and built-in protocols to specially named methods like __add__.
Python requires registering each operator with a decorator before the dunder method takes effect.
Python overloads operators only for numeric types, ignoring dunder methods on other objects.
What is the relationship between __eq__ and __hash__, and what happens if you override one but not the other?
Select the correct answer
Overriding __hash__ without __eq__ sets __eq__ to None, making instances incomparable
Overriding __eq__ automatically regenerates a matching __hash__ from the new equality logic
Overriding __eq__ without __hash__ sets __hash__ to None, making instances unhashable
Overriding either one leaves the other unchanged, so instances stay hashable and comparable
What is the __call__ method, and how does it let you make instances of a class callable?
Select the correct answer
Defining __call__ makes instances callable, so instance() invokes instance.__call__()
Defining __call__ makes the class callable, so calling it returns a new bound method
Defining __call__ overrides __init__ so construction and invocation share logic
Defining __call__ lets instances be called only when used as decorators on functions
How does Python determine the truthiness of an object, and what role do __bool__ and __len__ play?
Select the correct answer
Python treats every object as truthy unless it defines both __bool__ and __len__
Python uses __len__ if defined, else __bool__ (zero is falsy), else the object is falsy
Python checks __bool__ and __len__ together, treating an object true only if both agree
Python uses __bool__ if defined, else __len__ (zero is falsy), else the object is truthy
When would you use __slots__ in a class definition, and what are the trade-offs regarding memory and flexibility?
Select the correct answer
It makes all instance attributes immutable, trading flexibility for thread-safety guarantees.
It automatically generates getters and setters, reducing boilerplate at a small speed cost.
It caches attribute lookups for faster access but increases per-instance memory consumption.
It restricts attributes to a fixed set, saving memory but preventing dynamic attribute addition.
Explain the difference between __init__ and __new__. When would you specifically need to override __new__?
Select the correct answer
__new__ runs after __init__ and is used mainly to validate the constructed object.
__new__ creates and returns the instance; override it to control immutable type creation.
__init__ returns the new instance; override it when subclassing immutable built-in types.
__init__ creates the instance while __new__ only assigns attributes after construction.
Explain the difference between __getattr__ and __getattribute__.
Select the correct answer
__getattribute__ runs on every attribute access, while __getattr__ runs only when lookup fails
Both run only when an attribute is missing, but __getattribute__ is called first
Both run on every access, but __getattr__ takes priority over __getattribute__ each time
__getattr__ runs on every attribute access, while __getattribute__ runs only when lookup fails
What is the purpose of the abc module, and how does it help enforce an interface compared to standard inheritance?
Select the correct answer
Provides runtime checking of method arguments through decorators applied to entire classes.
Generates concrete default implementations for missing methods so subclasses always work.
Registers virtual subclasses but never prevents instantiation of incomplete child classes.
Defines abstract base classes that block instantiation until all abstract methods are implemented.
How does Python handle multiple inheritance? Explain the C3 Linearization algorithm and how super() determines which class to call next.
Select the correct answer
The MRO is computed by C3 linearization, and super() calls the next class in that order
The MRO is computed left-to-right depth-first, and super() always calls the direct parent
The MRO merges bases by inheritance depth, and super() calls the most derived base class
Each base is searched fully before the next, and super() calls the first listed base class
Explain how the Descriptor protocol works. How do @property, @classmethod, and @staticmethod use descriptors under the hood?
Select the correct answer
Descriptors define __init__ hooks, and property stores values directly in the instance __dict__
Descriptors define __getattr__/__setattr__, and property and classmethod wrap them as closures
Descriptors define __get__/__set__/__delete__, and property, classmethod, staticmethod are descriptors
Descriptors define __call__ only, and staticmethod and classmethod bind via metaclasses
What is a metaclass, and how does it differ from a standard class? When would you use a metaclass instead of a class decorator?
Select the correct answer
A metaclass is a special instance of a class used to cache attributes across many instances
A metaclass is a class decorator written in C that runs after the class is fully built
A metaclass is a base class shared by all classes and controls only instance creation logic
A metaclass is the class of a class and controls class creation, affecting all subclasses too
When would you use __init_subclass__ instead of a metaclass?
Select the correct answer
When you need a lightweight hook to customize subclasses without writing a full metaclass.
When you need to combine multiple metaclasses that would otherwise cause layout conflicts.
When you want to modify the class namespace before the class body has been executed.
When you must alter instance creation behavior each time a new object is instantiated.
What is the purpose of the typing module? Does Python enforce these types at runtime, and if not, why are they used in modern production codebases?
Select the correct answer
Provides type hints; Python ignores them at runtime, but they aid tooling and readability.
Provides runtime casting so values are coerced into their annotated types automatically.
Provides decorators that validate types only inside functions marked for strict checking.
Provides type hints that Python enforces at runtime, raising errors on mismatched arguments.
How does Python's 'Duck Typing' philosophy differ from 'Strong Typing'?
Select the correct answer
Duck typing allows implicit conversions; strong typing requires explicit declarations of all types.
Duck typing concerns having the right behavior; strong typing concerns not coercing unrelated types.
Duck typing checks types at compile time; strong typing defers every check until program runtime.
Duck typing forbids inheritance; strong typing relies entirely on class hierarchies for its safety.
Why would you use typing.Annotated?
Select the correct answer
To mark a type as deprecated so that static checkers warn whenever the annotation is being used.
To attach extra metadata to a type that tools or libraries can read without changing the type.
To combine two types into a union that validators check against at the time of a function call.
To convert a runtime value into its annotated type by coercing it during each assignment made.
What is the difference between Final and ClassVar in type hinting?
Select the correct answer
Final makes an object truly immutable at runtime, while ClassVar enforces the attribute's type while running.
Final marks a private attribute name, while ClassVar marks an attribute each instance copies on write.
Final marks a name that must not be reassigned, while ClassVar marks an attribute shared at the class level.
Final marks an attribute shared at the class level, while ClassVar marks a name that must not be reassigned.
What is 'Duck Typing', and how does it influence the way you design interfaces in Python compared to a strictly typed language like Java?
Select the correct answer
An object is automatically cast to its parent type at runtime, encouraging deep inheritance hierarchies over composition.
An object's type must be declared up front, encouraging explicit interface inheritance like Java abstract base classes.
An object's methods are checked by a compiler before running, encouraging strict static contracts over flexible code.
An object's suitability is judged by the methods it supports, encouraging informal protocols over declared interfaces.
Explain the difference between Nominal typing and Structural typing (Protocols) in Python.
Select the correct answer
Nominal matches by method signatures; structural matches by explicit subclass declarations.
Nominal checks types at runtime; structural checks types only during static analysis runs.
Nominal allows duck typing freely; structural forbids any implicit interface matching at all.
Nominal matches by declared class or inheritance; structural matches by the required methods.
What is the difference between typing.Any and object in a type hint?
Select the correct answer
Any restricts values to builtins; object accepts only user classes and their subclasses.
Any disables checking and is compatible both ways; object accepts all but allows few operations.
Any accepts all values but blocks operations; object disables checking and allows any operation.
Any and object behave identically except Any is faster during static analysis runs.
Explain the new Type Parameter Syntax introduced in PEP 695 (Python 3.12).
Select the correct answer
Adds a keyword that imports generics automatically without referencing the typing module at all.
Lets you enforce runtime generics so type arguments are checked whenever functions get called.
Lets you declare generic type parameters inline, like def f[T](x: T) -> T, without TypeVar.
Replaces all existing type hints with a new bracket syntax required for every annotated function.
How does Python's memory management work? Explain the difference between reference counting and the cyclic garbage collector, and how Python handles two objects that reference each other.
Select the correct answer
Reference counting frees objects on a fixed timer, while a cyclic collector reclaims objects whose count never changes at all.
Reference counting frees objects when their count hits zero, while a cyclic collector reclaims objects that reference each other.
Reference counting tracks the call stack depth, while a cyclic collector compacts the heap to remove memory fragmentation gaps.
Reference counting frees objects that reference each other, while a cyclic collector frees objects when their count hits zero.
What is the difference between the stack and the heap in the context of Python's memory management, and who manages the private heap?
Select the correct answer
The stack stores frames and references; the heap stores the objects, managed by Python's private memory manager.
The stack stores global variables; the heap stores local variables, managed jointly by the interpreter and CPU cache.
The stack stores large objects; the heap stores small immutable ones, managed by the cyclic garbage collector internally.
The stack stores all the objects; the heap stores only references, both managed directly by the operating system kernel.
What are 'Immortal Objects' (PEP 683) and how do they help with multi-processing performance?
Select the correct answer
Objects whose reference count is never changed, so their memory stays unchanged and shareable across forked processes.
Objects cached in a shared pool, so multiple processes can mutate them at the same time without acquiring any lock.
Objects that are never garbage collected, so each forked process must keep its own private duplicate copy in memory.
Objects pinned to a fixed memory address, so the operating system can map them faster between separate process spaces.
Why does Python not immediately release memory back to the operating system when an object is deleted?
Select the correct answer
It defers freeing so other running processes can read the old values directly from the abandoned memory blocks.
It keeps freed blocks in internal pools to reuse them, avoiding the overhead of repeated system calls to the OS.
It waits for the cyclic garbage collector to run on a timer before any freed memory can be returned to the OS.
It must keep every object alive until the interpreter exits, since reference counts can never truly reach zero.
How does generational garbage collection work in Python, and why does the collector move objects between 'Generation 0', '1', and '2'?
Select the correct answer
New objects start in generation 0; survivors are promoted to older generations, which are scanned less frequently.
Large objects start in generation 2; as they shrink they move to younger generations, which are scanned more often.
Objects move between generations by size, so generation 0 holds bytes, generation 1 kilobytes, and 2 megabytes.
Objects are sorted by reference count into generations, and only generation 0 with zero references is ever collected.
What is the difference between a generator and a list? Why are generators considered 'lazy'?
Select the correct answer
A list and a generator both store all elements, but a generator caches them for repeated reuse later
A list yields values lazily on demand, while a generator builds the full sequence up front eagerly
A list holds every element in memory, while a generator computes each value only when it is requested
A list computes values only when requested, while a generator holds all elements in memory at once
What are the memory benefits of using a generator expression over a list comprehension when processing a very large file?
Select the correct answer
A generator expression keeps only one item in memory at a time, while a list comprehension loads them all
A generator expression copies the file twice in memory, while a list comprehension reads it just once
A generator expression loads all items into memory at once, while a list comprehension keeps only one
Both load every line into memory, but a generator expression compresses them to reduce total usage
What is the difference between an iterable, an iterator, and a generator? What are the memory implications of using a generator over a list comprehension?
Select the correct answer
An iterator produces an iterable; a generator produces an iterator; all three load their entire contents into memory at once.
An iterable produces an iterator; an iterator yields items via next; a generator is a lazy iterator that saves memory.
An iterable yields items via next; an iterator produces an iterable; a generator builds the full list eagerly in memory.
An iterable and an iterator are identical; a generator differs only by syntax and stores every item in memory like a list.
How does the yield keyword work, and what is the difference between yield and yield from?
Select the correct answer
yield stops the generator permanently, while yield from restarts it from the beginning of the loop
yield creates a coroutine object, while yield from converts that coroutine into an awaitable async task
yield pauses and returns one value at a time, while yield from delegates iteration to a sub-iterable
yield returns all values at once eagerly, while yield from streams them one by one to save memory
What is the difference between an Iterator and an Iterable? How do you implement the Iterator protocol?
Select the correct answer
An iterable defines both __next__ and __iter__, while an iterator only needs __getitem__
An iterable defines __iter__ returning an iterator; an iterator defines __next__ to yield values
An iterator defines __iter__ returning an iterable; an iterable defines __next__ to yield values
An iterator defines __len__ and __iter__, while an iterable defines __next__ to produce values
What is the difference between a Coroutine and a standard Generator?
Select the correct answer
A generator receives values from its caller, while a coroutine only produces values and cannot be paused
A generator produces values to its caller, while a coroutine can also receive values and pause execution
A coroutine and a generator are identical, except coroutines must always be defined using the async keyword
A coroutine runs on multiple threads in parallel, while a generator runs strictly on a single thread only
What do coroutines and generators have in common, and how did generators evolve into async/await?
Select the correct answer
Neither can be paused mid-execution; async/await added pausing as an entirely separate language feature
Both return single values eagerly; async/await emerged from list comprehensions optimized for concurrency
Both can suspend and resume execution; async/await grew from generator coroutines built on yield from
Both run code in parallel threads; async/await replaced generators with new OS-level threading primitives
What do the send(), throw(), and close() methods do on a generator object?
Select the correct answer
send creates a new generator copy, throw logs an exception silently, close flushes its buffered values
send passes a value into the generator, throw raises an exception inside it, close terminates it
send returns the next value out, throw catches exceptions inside it, close pauses it temporarily
send restarts the generator from start, throw skips the current value, close caches its final result
What is the difference between *args and **kwargs, and how are they unpacked in a function call?
Select the correct answer
*args collects extra positional arguments as a tuple, while **kwargs collects extra keyword arguments as a dict
*args stores values as a list and **kwargs stores them as a set, both unpacked positionally
*args collects extra keyword arguments as a dict, while **kwargs collects extra positional arguments as a list
*args and **kwargs both collect positional arguments, but only into a tuple of pairs
What are the limitations of lambda functions compared to named functions, and when should they be avoided in production code?
Select the correct answer
A lambda cannot be assigned to a variable or passed around, making it unusable as a callback argument
A lambda cannot accept arguments or return values, so it should be avoided whenever data must be passed in
A lambda runs far slower than a def function because it is recompiled on every single call at runtime
A lambda is limited to a single expression, has no name or docstring, and hurts readability for complex logic
What is a list comprehension, and what are its advantages over using a for loop with append?
Select the correct answer
A concise expression building a list in one line, usually more readable and faster than append in a loop
A shorthand that compiles to identical bytecode as a loop, offering only cosmetic differences in style
A syntax that runs the loop body in parallel threads, making it strictly faster for any large input set
A lazy generator that yields list items on demand, saving memory by never building the full list at once
How do decorators work, and how can you ensure the original function's metadata is preserved?
Select the correct answer
A decorator subclasses the function object; the __metadata__ attribute restores its original name and docs
A decorator wraps a function returning a new one; functools.wraps preserves the original metadata
A decorator renames a function in place; functools.wraps copies the wrapper metadata onto its callers
A decorator executes a function immediately; functools.cache preserves the original function metadata
How do decorators work under the hood? Explain the concept of a closure and how it allows a decorator to remember the state of the wrapped function.
Select the correct answer
A decorator copies the source of the original function and recompiles it with extra behavior injected inline
A decorator returns a wrapper closure that captures the original function from the enclosing scope and calls it
A decorator stores the wrapped function in a global registry and looks it up by name at every call
A decorator mutates the original function object directly so the wrapper closure is never actually needed
Explain how a decorator works conceptually. How would you write a decorator that accepts its own arguments?
Select the correct answer
Attach the arguments as attributes on the wrapped function before the decorator returns the wrapper
Declare the arguments as global variables so the single decorator function can read them when it runs
Pass the arguments directly to the wrapper function and read them from *args at call time only
Write an outer factory taking the arguments that returns a decorator, which returns the wrapper function
What are dictionary and set comprehensions, and when would you use them?
Select the correct answer
Concise expressions building a sorted dict and an ordered set that always preserve insertion order
Concise expressions that build immutable frozen dicts and sets which cannot be modified after creation
Concise expressions building a dict of key-value pairs or a set of unique values from an iterable
Concise expressions that lazily produce dict and set items one at a time without building them fully
How does functools.lru_cache work, and how does it help with memoization?
Select the correct answer
It stores results on disk between runs so the cache survives even after the program fully restarts
It precomputes every possible result at import time and stores them in an unbounded lookup dictionary
It caches results keyed only by the function name, sharing one stored value across all argument sets
It caches results keyed by the call arguments, returning the stored value on repeated calls
What does functools.partial do, and when would you use it?
Select the correct answer
It merges two separate functions into one callable that applies both of them to the given arguments
It splits a function into smaller callables that each run one statement of the original body in turn
It returns a callable that runs only part of a function and defers the remaining lines until later
It returns a new callable with some arguments of an existing function already fixed in advance
What is functools.reduce, and how does it differ from a simple loop?
Select the correct answer
It applies a two-argument function cumulatively across an iterable to produce a single accumulated result
It filters an iterable by a predicate and returns only the items for which the function is true
It splits an iterable into chunks and returns a nested list of accumulated partial sums
It applies a one-argument function to every element and returns a new lazy iterator of results
What is the itertools module, and what kinds of problems does it help solve?
Select the correct answer
A standard library of fast, memory-efficient iterator building blocks for combinatorics and looping
A built-in collection of mutable container types that replace lists, sets, and dictionaries
A third-party library of vectorized array operations for numerical and statistical data analysis
A standard library of functions for parsing, formatting, and converting iterable data structures
What is the walrus operator (:=), and in what situations is it useful?
Select the correct answer
It declares a variable as global inside an expression, useful for sharing state across functions
It unpacks an iterable into a variable within a statement, useful for splitting function arguments
It assigns a value to a variable as part of an expression, useful in comprehensions and while loops
It compares two values for identity and binds the result, useful when checking object equality
What are positional-only and keyword-only parameters, and how do the / and * markers in a function signature work?
Select the correct answer
Parameters between / and * must always be passed strictly as keywords
Parameters before * are positional-only and those after / are keyword-only
Parameters before / are positional-only and those after * are keyword-only
Parameters after / are positional-only and those before * are keyword-only
How does the sorted() function differ from the list.sort() method, and what is the role of the key parameter?
Select the correct answer
sorted() sorts in place while list.sort() returns a copy; key decides whether the order is ascending
Both sort in place, but sorted() works on any iterable; key reverses the final ordering of items
sorted() returns a new list while list.sort() sorts in place; key sets what each item is sorted by
Both return new lists, but list.sort() is faster; key provides a comparison function taking two arguments
How do context managers work, and what is the difference between the __enter__/__exit__ and @contextmanager approaches?
Select the correct answer
Both require a class; @contextmanager simply renames __enter__ and __exit__ to shorter aliases
Both run on entry only; @contextmanager handles cleanup automatically while the methods need explicit calls
Both manage setup and teardown; @contextmanager uses a generator with yield instead of two methods
Both are async by default; @contextmanager provides synchronous behavior while the methods stay coroutine-based
What is Structural Pattern Matching (match/case) and how does it differ from a standard if/elif/else block?
Select the correct answer
It compiles a chain of conditions into a jump table for speed but otherwise behaves like nested branches
It evaluates every case in parallel and runs all blocks whose pattern happens to match the subject
It compares a value only for strict equality against constants, like a switch statement in other languages
It matches a value against structural patterns, destructuring and binding parts rather than just testing booleans
What does it mean that Python's sort is stable, and why does that matter?
Select the correct answer
Repeated sorts of the same list always run in identical time complexity
Elements comparing equal retain their original relative order after sorting
The sort never crashes or raises errors even with mixed-type elements present
Sorting always produces the same output regardless of the input order given
What is serialization with the pickle module, and what are its security concerns?
Select the correct answer
It compresses objects into bytes and automatically validates their integrity on load
It serializes objects to bytes; unpickling untrusted data can run arbitrary code
It serializes objects to JSON text and is always safe to load from external sources
It encrypts objects into bytes so untrusted sources cannot read or alter the data
How do you implement an asynchronous context manager (async with), and how do __aenter__ and __aexit__ differ from their synchronous counterparts?
Select the correct answer
Define __enter__ and __exit__ with async def so the event loop schedules them automatically
Define __aenter__ and __aexit__ as coroutines that can be awaited during setup and teardown
Define __aenter__ only, since async teardown reuses the synchronous __exit__ method unchanged
Define __aenter__ and __aexit__ as ordinary methods that return awaitable futures via a callback
What is the Global Interpreter Lock (GIL), and given its existence, how do you write concurrent code that actually runs in parallel across multiple CPU cores?
Select the correct answer
Use multiprocessing or C extensions that release the GIL to use multiple cores
Use the threading module since each thread runs on a separate CPU core in parallel
Increase the number of threads beyond the core count to bypass the GIL limitation
Use asyncio coroutines because the event loop schedules tasks across all CPU cores
Are Python threads 'real' native threads?
Select the correct answer
Yes, they are OS threads and the GIL lets them run bytecode fully in parallel
No, they are cooperative coroutines that yield control only at await points
Yes, they are real OS threads, but the GIL prevents parallel bytecode execution
No, they are green threads scheduled entirely by the Python interpreter itself
What is the GIL, and why was it originally implemented?
Select the correct answer
A queue ordering I/O operations across threads, added to guarantee deterministic execution order.
A mutex letting only one thread run Python bytecode at once, added to simplify memory management.
A scheduler distributing bytecode evenly across all CPU cores, added to maximize parallel speed.
A lock preventing multiple processes from sharing memory, added to improve security isolation.
What is the difference between a thread and a coroutine in the context of the Python runtime?
Select the correct answer
Threads suspend only at chosen await points; coroutines can be interrupted at any instruction.
Threads are scheduled preemptively by the OS; coroutines are scheduled cooperatively in one thread.
Threads always bypass the GIL for true parallelism; coroutines are limited to one core by the GIL.
Threads run in separate processes with isolated memory; coroutines share a single global process pool.
Why is multiprocessing often preferred over threading for CPU-bound tasks in CPython?
Select the correct answer
Each process has its own interpreter and GIL, so they run Python bytecode in true parallel.
Processes are lighter weight than threads, so spawning many of them costs almost nothing.
Threads cannot execute C extensions, while processes release the GIL during numeric loops.
Processes share memory faster than threads, reducing the copying overhead of CPU-bound work.
What is the experimental 'free-threaded' build in Python 3.13 (PEP 703), and what are the trade-offs of removing the GIL for existing C extensions?
Select the correct answer
It replaces the GIL with multiprocessing so all C extensions work unchanged and faster
It adds a second GIL per thread, doubling parallelism without affecting any C extensions
It compiles Python to machine code, removing the need for any C extension changes at all
It removes the GIL so threads run in parallel, but some C extensions need updates
Compare and contrast threading, multiprocessing, and asyncio. When is each appropriate?
Select the correct answer
threading and asyncio suit I/O-bound work; multiprocessing suits CPU-bound parallelism
asyncio suits CPU-bound work while threading and multiprocessing handle I/O-bound tasks
multiprocessing suits I/O-bound work while threading handles all CPU-bound parallel tasks
all three run CPU-bound code in parallel; the choice depends only on code readability
Explain what the GIL is and how it impacts multithreading in Python. If Python has a GIL, when would you still choose to use the threading module over multiprocessing?
Select the correct answer
Threads share memory cheaply, so use them for I/O-bound tasks where the GIL is released
Threads avoid the GIL entirely, so use them whenever fast inter-process messaging is needed
Threads run truly in parallel, so use them for CPU-bound tasks that need multiple cores
Threads spawn faster than processes, so use them only for heavy CPU-bound computation work
What is the difference between a per-interpreter GIL introduced in 3.12 and the traditional GIL?
Select the correct answer
The per-interpreter GIL removes locking entirely so all threads now run in parallel
The per-interpreter GIL replaces processes with threads sharing global state safely
Each subinterpreter has its own GIL, allowing separate interpreters to run in parallel
Each thread shares one GIL but subinterpreters bypass it for parallel execution
Explain the difference between preemptive multitasking (threading) and cooperative multitasking (asyncio). When is asyncio preferred over standard threading?
Select the correct answer
Threads share no memory; asyncio shares memory freely, best for parallel number-crunching tasks.
Threads run on many cores; asyncio also uses many cores, best for true CPU-bound parallelism.
Threads yield only at await points; asyncio preempts anytime, best for heavy CPU computations.
The OS switches threads anytime; asyncio yields at await, best for many I/O-bound connections.
How does asyncio achieve concurrency without using multiple threads or processes?
Select the correct answer
A single thread interleaves tasks at await points while waiting on non-blocking I/O via the loop.
The OS time-slices coroutines across cores, switching whenever a task has run long enough to yield.
The interpreter forks lightweight processes per coroutine that communicate through the event loop.
A pool of hidden threads runs each coroutine, and the loop merges their results when they finish.
When should you choose asyncio over threading for a high-concurrency task?
Select the correct answer
When handling many simultaneous I/O-bound connections that spend most time waiting on the network.
When you need true parallel execution of bytecode across threads without being limited by the GIL.
When running CPU-heavy computations that must fully utilize every available processor core at once.
When each task blocks on long synchronous calls that cannot be rewritten to use non-blocking APIs.
What is the difference between asyncio.gather() and asyncio.wait()?
Select the correct answer
gather() only accepts coroutines whereas wait() only accepts created future objects.
gather() collects ordered results and propagates errors; wait() returns done and pending sets.
gather() runs tasks sequentially while wait() runs every scheduled task concurrently.
gather() cancels all tasks on first error while wait() always waits for every task.
How does the asyncio event loop work, and what happens to the loop when you execute a blocking I/O call inside an async function?
Select the correct answer
The loop preempts tasks on a timer; a blocking call is paused and resumed later without blocking.
The loop runs ready tasks until each awaits; a blocking call freezes the whole loop and all tasks.
The loop polls tasks on parallel cores; a blocking call is automatically offloaded to a worker pool.
The loop spawns a thread per task; a blocking call only delays that one task, not the others.
What makes an object awaitable in Python? Explain the relationship between coroutines, Tasks, and Futures.
Select the correct answer
Any object passed to await is awaitable; coroutines, Tasks, and Futures are three names for one class.
An object defining __await__ is awaitable; a Task schedules a coroutine and subclasses Future.
An object defining __next__ is awaitable; a Future schedules a Task that wraps a coroutine.
An object with a callback is awaitable; Tasks are low-level results that Futures schedule on the loop.
How does the await keyword actually work under the hood?
Select the correct answer
It starts a new OS thread for the coroutine and joins it once the work is done.
It invokes the awaitable's __await__ and yields control to the event loop until completion.
It schedules a callback and returns a future immediately without suspending anything.
It blocks the current thread entirely until the awaited coroutine finishes running.
What is the difference between f-strings, str.format(), and %-formatting?
Select the correct answer
f-strings and % evaluate at runtime, while str.format() is resolved at compile time.
All three are identical in behavior and differ only in the surrounding quote characters.
Only str.format() supports embedding expressions; f-strings just concatenate plain strings.
f-strings embed expressions inline, while str.format() and % use placeholder substitution.
What is the difference between a module and a package, and what role does __init__.py play?
Select the correct answer
Modules and packages are identical, and __init__.py only stores documentation strings.
A package is a single file while a module is a directory containing several scripts.
A module must contain __init__.py while a package needs no special marker file.
A module is a single file; a package is a directory whose __init__.py marks it.
What is the difference between a shallow copy and a deep copy, and how does the copy module handle nested objects in each case?
Select the correct answer
A shallow copy duplicates every nested object while a deep copy shares them by reference.
A deep copy duplicates only the outer object while leaving inner references untouched.
A shallow copy duplicates the outer object but shares nested objects by reference.
Both copy types fully duplicate nested objects but differ only in execution speed.
How does Python handle circular imports, and what are the common strategies to resolve them?
Select the correct answer
Move imports inside functions or restructure modules to avoid partially initialized references.
Python automatically merges the two modules into one to silently break the cycle.
Duplicate the shared code into both modules since imports between them are impossible.
Circular imports always raise an immediate fatal error that cannot be worked around.
Why are strings immutable in Python, and what implications does that have for performance and memory?
Select the correct answer
Immutability makes string edits faster in place while increasing total memory usage.
Immutability prevents strings from being used as dictionary keys or set members.
Immutability allows safe sharing and hashing, but edits create new string objects.
Immutability means concatenation modifies the original buffer without new allocations.
How does the Python import system work, and what happens when you import a module for the second time?
Select the correct answer
The module file is read again and its top-level code is re-executed completely.
The interpreter raises an error since importing the same module twice is forbidden.
A fresh independent module object is created each time leaving the cache unchanged.
The cached module in sys.modules is reused and its code is not executed again.