Advanced Python & Libraries - MCQ Practice Test

Practice MCQs on advanced Python concepts like GIL and Descriptors for interview preparation.

This is a practice MCQ quiz to test your advanced Python and libraries knowledge for interview preparation. Answer all questions and submit to see your results.

Before diving into the MCQ, let’s briefly touch upon a few key advanced theoretical concepts that often underpin questions about libraries and system design.

Q1: Explain the Python Global Interpreter Lock (GIL) and its implications for multi-threaded applications.

A: The Global Interpreter Lock (GIL) is a mutex (mutual exclusion lock) that protects access to Python objects, preventing multiple native threads from executing Python bytecodes simultaneously. This means that even on multi-core processors, only one thread can execute Python bytecode at any given time, effectively turning CPU-bound multi-threaded Python programs into single-threaded ones in terms of actual CPU utilization for Python code.

The GIL exists in CPython (the most common implementation) primarily to simplify memory management and avoid race conditions on shared Python objects by the interpreter itself, making CPython easier to develop and integrate with C libraries.

Key Points:

  • Single-threaded bytecode execution: Only one Python thread runs at a time.
  • Impact on CPU-bound tasks: Significantly limits parallelism for CPU-bound tasks in Python.
  • Limited impact on I/O-bound tasks: For I/O-bound tasks (network requests, disk I/O), the GIL is released during I/O operations, allowing other threads to run while one waits.
  • Workarounds: For CPU-bound parallelism, use multiprocessing (which spawns separate Python processes, each with its own GIL) or external C/Rust libraries that release the GIL. Asynchronous programming (asyncio) is excellent for I/O-bound concurrency but does not bypass the GIL for CPU-bound tasks.
  • Future of GIL: There’s ongoing work (e.g., the “No-GIL” fork, PEP 703) to remove or make the GIL optional in future Python versions (potentially Python 3.13 or later), but as of 2026-01-16, it is a core feature of CPython.

Common Mistakes:

  • Believing multi-threading in Python offers true parallel execution for CPU-bound tasks.
  • Confusing asyncio with a GIL-bypassing mechanism for CPU-bound work.
  • Underestimating the performance implications of the GIL for parallel computational problems.

Follow-up:

  • How would you achieve true parallel processing for a CPU-bound task in Python?
  • When would you choose threading over multiprocessing in Python?
  • Discuss the performance characteristics of asyncio in relation to the GIL.

Q2: Describe the purpose and common use cases of Python Descriptors.

A: Descriptors are objects that implement one or more of the descriptor protocol methods (__get__, __set__, __delete__). They allow for customized behavior when an attribute is accessed, modified, or deleted on an instance of a class. Descriptors are a powerful mechanism for implementing reusable property logic, type validation, or lazy loading without scattering logic across multiple methods or duplicating code.

Common Use Cases:

  • property decorator: The built-in property decorator is implemented using descriptors, allowing getter, setter, and deleter methods for an attribute.
  • Type Validators: Ensuring an attribute is always of a certain type or conforms to specific constraints.
  • Lazy Loading: Loading an attribute’s value only when it’s first accessed, saving resources.
  • Managed Attributes: Creating attributes whose access is controlled by external logic, such as database fields or cached values.
  • classmethod and staticmethod: These are also implemented as descriptors.

Key Points:

  • Protocol methods: __get__(self, instance, owner), __set__(self, instance, value), __delete__(self, instance).
  • Binding behavior: Descriptors “bind” to instance attributes during access.
  • Data vs. Non-Data Descriptors: A descriptor that defines __set__ or __delete__ is a “data descriptor.” If it only defines __get__, it’s a “non-data descriptor.” Data descriptors take precedence over instance dictionaries, while non-data descriptors do not.

Common Mistakes:

  • Not understanding the difference between data and non-data descriptors and how they resolve attribute lookups.
  • Over-engineering with descriptors when a simple property or method would suffice.
  • Trying to use descriptors on the class itself rather than on instances (they are primarily for instance attributes).

Follow-up:

  • How does the property decorator utilize the descriptor protocol?
  • When would you prefer a custom descriptor over the property decorator?
  • Explain the attribute lookup order in Python, particularly how descriptors fit in.

Q3: What is the role of asyncio in modern Python development, and how does it achieve concurrency?

A: asyncio is Python’s built-in library for writing concurrent code using the async/await syntax. It is a framework for writing single-threaded concurrent code using coroutines, event loops, and non-blocking I/O. Its primary role is to efficiently handle a large number of I/O-bound operations (like network requests, database queries, file I/O) without blocking the main execution thread.

asyncio achieves concurrency through cooperative multitasking, also known as an event loop model. Instead of threads, asyncio uses coroutines (functions defined with async def), which are essentially resumable functions. When an await keyword is encountered inside a coroutine, it signals to the event loop that the coroutine is performing an I/O operation and will temporarily pause its execution, yielding control back to the event loop. The event loop can then switch to another waiting coroutine that is ready to run. Once the I/O operation for the paused coroutine completes, the event loop can resume its execution. Because the system switches between tasks at explicit await points, this is called cooperative multitasking, as opposed to pre-emptive multitasking used by threads.

Key Points:

  • Coroutines: Functions defined with async def that can be paused and resumed.
  • Event Loop: The central orchestrator that schedules and executes coroutines.
  • Non-blocking I/O: Crucial for efficient handling of many concurrent operations.
  • Cooperative Multitasking: Coroutines explicitly yield control (via await) to the event loop.
  • Ideal for I/O-bound: Highly efficient for tasks that spend most of their time waiting for external resources.
  • Single-threaded: Does not bypass the GIL for CPU-bound tasks; it’s about efficient switching of tasks on a single thread.

Common Mistakes:

  • Assuming asyncio provides true parallel execution for CPU-bound tasks.
  • Forgetting to await coroutines, which can lead to coroutine objects not being run.
  • Mixing asyncio with traditional blocking I/O calls without using executor pools, which can block the event loop.
  • Misunderstanding that async def alone doesn’t make code concurrent; it needs to be scheduled by an event loop.

Follow-up:

  • How would you integrate a synchronous, CPU-bound function into an asyncio application without blocking the event loop?
  • Compare and contrast asyncio with Python’s threading and multiprocessing modules. When would you use each?
  • What is the purpose of await asyncio.sleep(0)?

Advanced Python & Libraries MCQ Test

This section contains multiple-choice questions testing your knowledge of advanced Python features and common libraries as of 2026.


Q1: Which of the following statements about the Python Global Interpreter Lock (GIL) is TRUE as of early 2026 for CPython? A. The GIL allows multiple CPU-bound Python threads to execute simultaneously on multi-core processors. B. The GIL is primarily released during I/O operations, making threading efficient for I/O-bound tasks. C. The GIL is a mechanism present in all Python implementations (e.g., Jython, IronPython, PyPy). D. asyncio programs bypass the GIL, enabling true parallel execution of CPU-bound coroutines.

Correct Answer: B Explanation:

  • A is incorrect: The GIL prevents multiple Python threads from executing bytecode simultaneously, even on multi-core systems.
  • B is correct: During I/O operations (like network requests or file reads), the GIL is released, allowing other Python threads to run while the current one waits. This makes Python’s threading module useful for I/O-bound tasks.
  • C is incorrect: The GIL is specific to CPython and some other implementations; Jython and IronPython do not have a GIL, and PyPy has its own unique approach.
  • D is incorrect: asyncio provides concurrency for I/O-bound tasks by cooperatively switching between coroutines on a single thread. It does not bypass the GIL for CPU-bound operations.

Q2: You are working with a large Pandas DataFrame df. Which of the following operations is generally the most performant way to apply a custom, element-wise function my_func to a column named ‘Value’ for high-performance data manipulation? A. df['Value'].apply(my_func) B. Using a Python for loop to iterate through df['Value'] and apply my_func. C. Vectorizing my_func using NumPy operations or a library like numba if my_func is computationally intensive. D. df['Value'].map(my_func)

Correct Answer: C Explanation:

  • A is generally slow: While apply is convenient, it often iterates over Python objects and is slower than vectorized operations, especially for large DataFrames.
  • B is the slowest: Explicit Python for loops are almost always the least performant way to process large datasets in Pandas due to high Python overhead.
  • C is correct: Pandas (and NumPy) are designed for vectorized operations, which are implemented in highly optimized C code. If a custom function can be rewritten using NumPy’s array operations or if performance is critical for a CPU-bound function, using numba to JIT-compile it or restructuring for vectorized operations will provide the best performance.
  • D is generally faster than apply for Series, but still not as fast as vectorization: map on a Series is typically more optimized than apply for element-wise operations, but still operates on Python objects. Vectorization remains superior for performance.

Q3: In Python’s asyncio framework, what is the primary purpose of the await keyword? A. To immediately execute a synchronous function in a separate thread. B. To pause the execution of the current coroutine and yield control to the event loop, allowing other tasks to run. C. To force the execution of a CPU-bound task in parallel. D. To define a new asynchronous function.

Correct Answer: B Explanation:

  • A is incorrect: await pauses a coroutine; it doesn’t execute synchronous functions in threads. For that, you’d typically use loop.run_in_executor().
  • B is correct: await is the mechanism by which a coroutine explicitly signals to the asyncio event loop that it is waiting for an I/O operation or another coroutine to complete. This allows the event loop to switch to other ready tasks, achieving cooperative concurrency.
  • C is incorrect: asyncio is for I/O-bound concurrency on a single thread, not CPU-bound parallelism.
  • D is incorrect: async def defines an asynchronous function (a coroutine), not await.

Q4: You are designing an API using a modern Python web framework (e.g., FastAPI, Django REST Framework). To enforce that a specific endpoint can only be accessed by authenticated users with an ‘admin’ role, which design pattern or feature would you most likely employ? A. Aspect-Oriented Programming (AOP) with method interception. B. Custom Decorators or Middleware. C. Using Python’s logging module to check user roles. D. Storing role information directly in the HTTP request body.

Correct Answer: B Explanation:

  • A is too generic and not the primary Pythonic way: While AOP concepts might be present, decorators and middleware are the direct Pythonic implementations for this.
  • B is correct:
    • Decorators: In Python web frameworks, decorators (e.g., @login_required, @permission_required) are commonly used to add functionality like authentication and authorization checks to view functions or methods.
    • Middleware: Middleware components intercept requests before they reach the view function and responses after they leave, making them ideal for global concerns like authentication, logging, or role-based access control.
  • C is incorrect: The logging module is for recording events, not for enforcing access control.
  • D is incorrect and insecure: Storing sensitive access control information like roles directly in the request body, especially for enforcement, is insecure and against common API design principles. Role information is typically part of the authenticated user’s session or token.

Q5: Consider the following Python code snippet:

from collections import defaultdict

data = [('A', 1), ('B', 2), ('A', 3), ('C', 4), ('B', 5)]
grouped_data = defaultdict(list)
for key, value in data:
    grouped_data[key].append(value)

print(grouped_data['D'])

What will be the output of this code? A. KeyError: 'D' B. [] C. None D. 0

Correct Answer: B Explanation:

  • A is incorrect: A defaultdict automatically creates an entry for a missing key using the default factory provided during its initialization.
  • B is correct: In this case, defaultdict(list) means that if you try to access a key that doesn’t exist, it will automatically create that key and assign an empty list ([]) as its value, then return that empty list.
  • C and D are incorrect: None and 0 are not the default value provided by list for a defaultdict.

Q6: You are writing a highly configurable Python application. You want to provide a way for users to register custom functions that will be called at specific points in your application’s lifecycle, without modifying the core logic. Which Python pattern is best suited for this scenario? A. Inheritance with Abstract Base Classes. B. Dependency Injection with explicit factory functions. C. Decorators or a Plugin System. D. Using exec() and eval() for runtime code execution.

Correct Answer: C Explanation:

  • A is less flexible: While inheritance can extend functionality, it requires the user to subclass your base classes, which might be overly restrictive for simple function registration.
  • B is plausible but less direct for registering functions: Dependency injection primarily focuses on providing dependencies to components. While a factory could create custom components, a decorator or plugin system is more idiomatic for registering functions at specific points.
  • C is correct:
    • Decorators: A decorator can wrap user-defined functions and register them with a central registry in your application. For example, @app.on_startup or @plugin_manager.register.
    • Plugin System: A more elaborate version might involve defining entry points or loading modules dynamically that contain functions registered via decorators. Both allow external code to “hook into” your application.
  • D is insecure and generally discouraged: exec() and eval() are dangerous for user-provided code as they can execute arbitrary code, posing significant security risks.

Q7: Which of the following is a key characteristic of a Python data descriptor? A. It only defines the __get__ method. B. It takes precedence over an instance’s __dict__ when resolving an attribute lookup. C. It is typically stored in the instance’s __dict__. D. It cannot be used with the property built-in function.

Correct Answer: B Explanation:

  • A is incorrect: A non-data descriptor only defines __get__. A data descriptor defines __set__ or __delete__ (and usually __get__ as well).
  • B is correct: Data descriptors have higher precedence in the attribute lookup chain. If a class defines a data descriptor for an attribute x, accessing instance.x will always invoke the descriptor’s __get__ method, even if instance.__dict__ has an x entry.
  • C is incorrect: Descriptors are typically defined at the class level, and their methods are invoked when attributes are accessed on instances of that class. The descriptor object itself is not usually stored in the instance’s __dict__ for each instance.
  • D is incorrect: The built-in property function is a prime example of a data descriptor, demonstrating that they are indeed used with it.

Q8: You need to efficiently calculate the dot product of two large 1D arrays (vectors) in Python. Which library provides the most optimized way to perform this operation? A. Python’s built-in math module. B. The collections module. C. The NumPy library. D. Python’s standard list operations with a generator comprehension.

Correct Answer: C Explanation:

  • A is incorrect: The math module provides scalar mathematical functions, not array operations.
  • B is incorrect: The collections module provides specialized container datatypes, not numerical array processing.
  • C is correct: NumPy is the fundamental package for numerical computing with Python. It provides highly optimized array objects and a vast collection of functions for operating on these arrays, including efficient vector operations like the dot product, which are implemented in C/Fortran for speed.
  • D is inefficient: While a generator comprehension could compute the sum of products, it would involve Python-level loops and object creation for each element, making it significantly slower than NumPy’s vectorized operations for large arrays.

Q9: In a FastAPI application, what is the primary purpose of defining path parameters (e.g., @{app.get("/items/{item_id}")})? A. To specify optional query parameters that can be passed in the URL. B. To define the type of HTTP request method (GET, POST, PUT, DELETE). C. To extract variable segments from the URL path as arguments to the path operation function. D. To validate the entire request body against a Pydantic model.

Correct Answer: C Explanation:

  • A is incorrect: Query parameters are typically defined using function arguments that are not part of the path string (e.g., q: Optional[str] = None).
  • B is partially correct but not the primary purpose of path parameters: The @app.get part defines the HTTP method, but {item_id} specifically defines a path parameter.
  • C is correct: Path parameters, enclosed in curly braces in the path string, allow FastAPI (and other web frameworks) to capture variable parts of the URL and pass them as arguments to the corresponding path operation function. For example, in /items/123, 123 would be extracted as item_id.
  • D is incorrect: Request body validation is handled by defining Pydantic models as function parameters without path parameter syntax.

Q10: Which module from Python’s standard library is best suited for executing external commands and interacting with their input/output streams securely and flexibly? A. os.system() B. subprocess C. shutil D. pathlib

Correct Answer: B Explanation:

  • A is generally discouraged: os.system() is a simple way to run a command but is less secure and less flexible, as it does not easily allow capturing output, handling errors, or passing arguments safely (it relies on the shell).
  • B is correct: The subprocess module is the recommended way to spawn new processes, connect to their input/output/error pipes, and obtain their return codes. It offers much more control, security, and flexibility compared to os.system().
  • C is incorrect: shutil provides high-level file operations (copying, moving, deleting files/directories), not general process execution.
  • D is incorrect: pathlib offers an object-oriented way to interact with file system paths, not for running external commands.

Mock Interview Snippet: Advanced Python Utilities

Scenario Setup: You’ve just completed a coding challenge that involved processing a large dataset. The interviewer now wants to quickly assess your knowledge of advanced Python features and standard library utilities that could optimize or improve the robustness of such a solution.

Interviewer: “Great work on the data processing task. Now, let’s pivot slightly. Imagine a scenario where you’re dealing with a large stream of numerical data coming in, and you need to perform some aggregate calculations and also cache results efficiently. I’ll throw a few quick questions at you.

Interviewer Question 1: “Suppose you have a function that performs a complex, CPU-bound calculation, and you want to ensure its results are cached based on its arguments to avoid recomputing for the same inputs. Which Python standard library module would you leverage, and how?”

Candidate’s Expected Response (Key Points):

  • Module: functools.
  • Decorator: Specifically, the @functools.lru_cache decorator.
  • Explanation: lru_cache provides a memoization decorator that caches the results of a function call. It’s a Least-Recently-Used (LRU) cache, meaning if the cache reaches its maximum size, it discards the least recently used entries.
  • Usage: You’d apply it directly above the function definition:
    import functools
    
    @functools.lru_cache(maxsize=128) # or None for unbounded cache
    def expensive_calculation(arg1, arg2):
        # ... perform complex calculation ...
        return result
    
  • Considerations: Function arguments must be hashable. Be mindful of maxsize to control memory usage.

Interviewer Question 2: “Now, let’s say this stream of data frequently contains groups of identical items that you need to process together. For instance, ['apple', 'banana', 'apple', 'orange', 'banana'], and you want to easily iterate over all ‘apples’, then all ‘bananas’, etc. Which standard library tool is ideal for grouping consecutive identical elements?”

Candidate’s Expected Response (Key Points):

  • Module: itertools.
  • Function: itertools.groupby.
  • Explanation: groupby takes an iterable and an optional key function. It returns an iterator that yields consecutive keys and groups from the iterable. The key function is used to determine if two elements are considered “the same” for grouping purposes. Crucially, the input iterable must already be sorted by the grouping key for groupby to work as expected on all identical items, not just consecutive ones.
  • Usage:
    from itertools import groupby
    
    data = ['apple', 'banana', 'apple', 'orange', 'banana']
    # If not sorted, only consecutive identical items are grouped:
    # for key, group in groupby(data):
    #    print(f"{key}: {list(group)}") # This would group 'apple' then 'banana', then 'apple' again.
    
    # To group all identical items, sort first:
    sorted_data = sorted(data) # ['apple', 'apple', 'banana', 'banana', 'orange']
    for key, group in groupby(sorted_data):
        print(f"{key}: {list(group)}")
    
  • Follow-up consideration: The interviewer might ask about handling unsorted data. This would lead to discussing sorted(data) prior to groupby or using collections.Counter or collections.defaultdict.

Interviewer Question 3: “Finally, you have a set of asynchronous I/O operations you need to perform concurrently, but you also have a few CPU-bound helper functions that are blocking. How would you integrate these blocking CPU-bound functions into your asyncio event loop without blocking it, ensuring your I/O operations remain responsive?”

Candidate’s Expected Response (Key Points):

  • Mechanism: Use an executor, specifically loop.run_in_executor().
  • Explanation: The asyncio event loop is single-threaded and should not be blocked. For CPU-bound or blocking I/O functions that cannot be rewritten as coroutines, run_in_executor() offloads these tasks to a separate thread pool (ThreadPoolExecutor) or process pool (ProcessPoolExecutor). This allows the event loop to continue processing other coroutines while the blocking task runs in the background.
  • Default Executor: By default, run_in_executor() uses a ThreadPoolExecutor. For truly CPU-bound tasks that need to bypass the GIL, a ProcessPoolExecutor would be more appropriate.
  • Usage:
    import asyncio
    import time
    from concurrent.futures import ProcessPoolExecutor # or ThreadPoolExecutor
    
    def blocking_cpu_task(duration):
        print(f"Starting blocking CPU task for {duration} seconds...")
        time.sleep(duration) # Simulate CPU-bound work
        print(f"Finished blocking CPU task for {duration} seconds.")
        return f"CPU result after {duration}s"
    
    async def async_io_task(name, delay):
        print(f"Starting async I/O task {name} for {delay} seconds...")
        await asyncio.sleep(delay)
        print(f"Finished async I/O task {name}.")
        return f"I/O result from {name}"
    
    async def main():
        loop = asyncio.get_running_loop()
        # Using a ProcessPoolExecutor for CPU-bound tasks to bypass GIL
        with ProcessPoolExecutor() as executor:
            cpu_future = loop.run_in_executor(executor, blocking_cpu_task, 3)
            io_future1 = async_io_task("Network Call 1", 1)
            io_future2 = async_io_task("Database Query", 2)
    
            results = await asyncio.gather(cpu_future, io_future1, io_future2)
            print("All tasks completed:", results)
    
    # if __name__ == "__main__":
    #     asyncio.run(main())
    
  • Key takeaway for interviewer: Demonstrates understanding of how to manage synchronous blocking code within an asynchronous context without hindering event loop responsiveness.

Practical Tips

  1. Deep Dive into Standard Library: Many “advanced” problems can be solved elegantly and efficiently with modules like collections, itertools, functools, concurrent.futures, asyncio, and unittest.mock. Spend time not just knowing they exist, but understanding their core functions and common use cases.
  2. Master Concurrency (async/await vs. threading vs. multiprocessing): This is a recurring theme for advanced roles. Understand the GIL’s implications and when to use asyncio (I/O-bound, cooperative), threading (I/O-bound, OS pre-emptive, still GIL-limited for CPU), or multiprocessing (CPU-bound, true parallelism).
  3. Explore Popular Libraries: For data science/engineering, be proficient in NumPy and Pandas. For web development, understand the core concepts of frameworks like FastAPI or Django/Flask, especially related to request/response handling, middleware, and database integration (e.g., SQLAlchemy).
  4. Practice System Design Thinking: Many advanced Python questions tie into larger system design problems. Think about how these advanced features or libraries contribute to scalability, reliability, and maintainability.
  5. Read Official Documentation: The official Python documentation, as well as documentation for major libraries (NumPy, Pandas, FastAPI, etc.), is an invaluable resource for understanding subtleties and best practices.
  6. Code Review and Open Source: Reviewing advanced Python code from open-source projects can expose you to real-world applications of complex patterns and libraries.
  7. Whiteboard Practice: Explain concepts like the GIL or descriptor protocol verbally and with diagrams. This simulates interview conditions and solidifies understanding.

Summary

This chapter challenged your understanding of advanced Python concepts, including the GIL, descriptors, and asynchronous programming with asyncio, alongside the practical application of key libraries like NumPy, Pandas, collections, functools, and FastAPI. Mastering these areas demonstrates a readiness for complex development challenges and a deeper appreciation for Python’s design philosophy.

As you progress, remember that technical interviews often probe not just what you know, but why and when to apply specific tools and patterns. Use the MCQ format as a rapid knowledge check, and the mock scenario to practice articulating your thought process under pressure. Continue to refine your understanding of performance, concurrency, and library best practices.

References

  1. Python Official Documentation (3.9+): https://docs.python.org/3/ (Always the authoritative source for language features and standard library)
  2. NumPy Documentation: https://numpy.org/doc/stable/ (For efficient numerical operations)
  3. Pandas Documentation: https://pandas.pydata.org/docs/ (For data manipulation and analysis)
  4. FastAPI Documentation: https://fastapi.tiangolo.com/ (For modern asynchronous web development)
  5. Real Python - Advanced Topics: https://realpython.com/ (Excellent tutorials and articles on various advanced Python topics)
  6. InterviewBit - Python Interview Questions: https://www.interviewbit.com/python-interview-questions/ (General Python interview questions)
  7. GeeksforGeeks - Python Quizzes: https://www.geeksforgeeks.org/python-quizzes/ (Additional MCQ practice)

This interview preparation guide is AI-assisted and reviewed. It references official documentation and recognized interview preparation resources.

Question 1
Which of the following statements about the Python Global Interpreter Lock (GIL) is TRUE as of early 2026 for CPython?
Question 2
You are working with a large Pandas DataFrame. Which operation is generally the most performant way to apply a custom function to a column?
Question 3
In Python's asyncio framework, what is the primary purpose of the await keyword?
Question 4
To enforce that an API endpoint can only be accessed by authenticated users with an 'admin' role, which pattern would you most likely employ?
Question 5
What will be the output when accessing a non-existent key in a defaultdict(list)?
Question 6
You want users to register custom functions at specific points in your application's lifecycle. Which pattern is best suited?
Question 7
Which of the following is a key characteristic of a Python data descriptor?
Question 8
Which library provides the most optimized way to calculate the dot product of two large 1D arrays?
Question 9
In a FastAPI application, what is the primary purpose of defining path parameters (e.g., @app.get('/items/{item_id}'))?
Question 10
Which module from Python's standard library is best suited for executing external commands and interacting with their input/output streams securely?