Python Interview Questions
Python interviews test your understanding of core language features, data structures, algorithms, and object-oriented programming. They often include conceptual questions to gauge your depth of knowledge, plus hands-on coding problems to assess practical skills. Whether you're a junior or senior developer, mastering these topics is crucial for success.
What Python interviews cover
Core Syntax & Data Structures
Questions on Python's built-in types like lists, dicts, sets, tuples, and string manipulation, including time complexity.
Object-Oriented Programming (OOP)
Covers classes, inheritance, polymorphism, and special methods like __init__, __str__, and property decorators.
Functional Programming & Comprehensions
Includes lambda, map, filter, reduce, list/dict comprehensions, and generator expressions.
Concurrency & Performance
Topics like GIL, threading vs multiprocessing, async/await, and profiling.
Sample Python interview questions
- What are Python's key features?What a strong answer covers
- Interpreted and interactive
- Dynamically typed with optional type hints
- High-level with automatic memory management
- Extensive standard library and third-party packages
- Object-oriented, functional, and procedural paradigms
View a sample answer
Python is an interpreted, high-level language that emphasizes readability and simplicity. Its dynamic typing allows variables to change type at runtime, though type hints can be added for clarity. Automatic memory management via garbage collection frees developers from manual memory handling. The extensive standard library includes modules for everything from web services to data processing, and a vast ecosystem of third-party packages exists via pip. Python supports multiple programming paradigms, making it versatile for scripting, automation, data science, web development, and more. Its interactive interpreter and REPL facilitate rapid prototyping and debugging. However, dynamic typing can lead to runtime errors, and performance may be slower than compiled languages.
- Explain the difference between list and tuple. When would you use each?What a strong answer covers
- List is mutable; tuple is immutable
- List uses square brackets; tuple uses parentheses
- Tuple can be used as dictionary keys; list cannot
- Tuple is generally faster due to immutability
View a sample answer
The primary difference is mutability: lists can be modified after creation (append, remove, update), while tuples are immutable and cannot be changed. Syntactically, lists use square brackets [ ] and tuples use parentheses ( ), though parentheses are optional in many contexts. Since tuples are immutable, they are hashable and can be used as dictionary keys or set elements, which is not possible with lists. Performance-wise, tuples are slightly faster than lists because they have a fixed size and simpler memory layout. Use a list when you need a sequence that may change in size or content, e.g., collecting items dynamically. Use a tuple for fixed collections like coordinates, database records, or function arguments that should not be modified, as immutability ensures data integrity and allows use in hash-based structures.
- How does Python manage memory? Discuss garbage collection and reference counting.What a strong answer covers
- Reference counting: each object tracks number of references
- Garbage collector: handles cyclic references
- Generational GC: divides objects into generations
- Memory is allocated in heap; Python manages dynamically
View a sample answer
Python manages memory primarily through reference counting. Each object maintains a count of references to it; when the count drops to zero, the memory is deallocated immediately. However, reference counting fails to handle cyclic references (e.g., two objects referencing each other). To address this, Python includes a cyclic garbage collector that periodically detects and collects unreachable cycles. The garbage collector uses a generational approach, dividing objects into three generations based on age; younger generations are collected more frequently to optimize performance. Memory is allocated from a private heap managed by the memory manager. Developers can influence garbage collection via the gc module. While reference counting is deterministic and low-latency, it can cause overhead from frequent increments/decrements. Understanding memory management helps in optimizing large applications and avoiding memory leaks.
- Write a function to find the second largest number in a list without using sorting.What a strong answer covers
- Iterate through list once, O(n) time
- Track first and second largest values
- Handle duplicates and edge cases (list too short)
- No sorting or extra data structures required
View a sample answer
To find the second largest number without sorting, you can iterate through the list once while keeping track of the largest and second largest values. Initialize both as negative infinity or None. For each number, update the largest and second largest accordingly. If a number is larger than the current largest, set second largest to the previous largest and update largest. Else if it is larger than the second largest and not equal to the largest, update second largest. After the loop, return second largest. Edge cases: if the list has fewer than two unique elements, raise an exception or return None. This approach runs in O(n) time and O(1) space, avoiding the O(n log n) cost of sorting.
Reference solutionpython def second_largest(nums): if len(nums) < 2: raise ValueError("List must have at least two elements") # Initialize with negative infinity first = second = float('-inf') for num in nums: if num > first: second = first first = num elif num > second and num != first: second = num if second == float('-inf'): raise ValueError("No second largest element (all same?)") return second # Time: O(n) | Space: O(1) - Implement a decorator that measures execution time of a function.What a strong answer covers
- Uses functools.wraps to preserve metadata
- Records start time, executes function, calculates elapsed
- Prints or logs the duration
- Can be applied with @ syntax
View a sample answer
A decorator that measures execution time wraps a function with timing logic. Inside the decorator, we define a wrapper function that records the start time using time.time() (or time.perf_counter() for higher precision), calls the original function, records the end time, calculates the difference, and prints or logs it. We use @functools.wraps to copy the original function's metadata (name, docstring, etc.) to the wrapper. The decorator is applied with @timer syntax above the function definition. This pattern is useful for profiling and optimization, but note that the time measurement includes the overhead of the decorator itself. For more accurate benchmarking, consider using timeit module.
Reference solutionpython import time from functools import wraps def timer(func): @wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) end = time.perf_counter() print(f"{func.__name__} took {end - start:.6f} seconds") return result return wrapper # Usage: # @timer # def some_function(): # ... # Time: O(1) overhead per call | Space: O(1) - What is the GIL? How does it affect multithreading?What a strong answer covers
- GIL stands for Global Interpreter Lock
- It ensures only one thread executes Python bytecode at a time
- Limits CPU-bound multithreading performance
- I/O-bound threads benefit because they release the GIL
- Can be avoided using multiprocessing or C extensions
View a sample answer
The Global Interpreter Lock (GIL) is a mutex that protects access to Python objects, preventing multiple native threads from executing Python bytecodes simultaneously. This simplifies memory management (e.g., reference counting) but severely limits CPU-bound multithreading, as only one thread can run at a time even on multi-core systems. For I/O-bound tasks (network, disk), threads often wait for I/O, during which they release the GIL, allowing other threads to run; thus multithreading still offers concurrency. To achieve parallelism for CPU-bound work, use the multiprocessing module (separate processes, each with its own GIL) or write performance-critical parts in C extensions that release the GIL. The GIL is a known limitation but not a problem for many applications. In Python 3.12, efforts to make the GIL optional are ongoing.
- Write a generator that yields Fibonacci numbers up to n.What a strong answer covers
- Generator uses yield to produce values lazily
- Iterates up to n, yielding Fibonacci numbers
- Efficient for large n (memory constant)
- Implementation: start with a=0, b=1, swap each iteration
View a sample answer
A generator function to yield Fibonacci numbers up to n uses the yield keyword to produce each number lazily, consuming constant memory regardless of how many numbers are generated. We start with two variables a=0 and b=1. With a while loop that runs while the next Fibonacci number is less than or equal to n, we yield a (or b, depending on definition) and update a,b = b, a+b. This approach avoids building a list, making it memory-efficient for large n. The generator can be consumed in a for loop or converted to a list if needed. Time complexity is O(n) since we compute each number once.
Reference solutionpython def fibonacci_up_to(n): a, b = 0, 1 while a <= n: yield a a, b = b, a + b # Usage: # for num in fibonacci_up_to(100): # print(num) # Time: O(n) | Space: O(1) - Explain the MRO (Method Resolution Order) in Python and how super() works.What a strong answer covers
- MRO is determined by C3 linearization algorithm
- Ensures consistent method resolution in diamond inheritance
- super() follows MRO to call next class in chain
- super() in single inheritance returns parent; in multiple inheritance follows MRO
View a sample answer
Method Resolution Order (MRO) in Python defines the order in which base classes are searched when a method is called. It uses the C3 linearization algorithm to create a consistent order that respects monotonicity and local precedence order. The MRO can be inspected with Class.__mro__ or Class.mro(). The super() function returns a proxy object that delegates method calls to the next class in the MRO. In single inheritance, super() simply refers to the parent class. In multiple inheritance, super() follows the MRO to ensure that each class's method is called once and in the correct order, avoiding ambiguous resolution. This is particularly useful for cooperative multiple inheritance (e.g., mixins) where classes may share method signatures. A common pitfall is forgetting to call super().__init__() in subclasses, which can break initialization chains. Understanding MRO is crucial for designing complex class hierarchies.
How to prepare
- Master built-in functions and standard library (e.g., itertools, collections).
- Practice coding problems on platforms like LeetCode or HackerRank with a focus on Python-specific solutions.
- Understand Python's memory model and how objects are passed (reference vs value).
- Be able to explain design patterns and Pythonic idioms (e.g., context managers, decorators).
- Review important data structures: dictionaries, sets, and their time complexities.
Frequently asked questions
What is the difference between `==` and `is`?
`==` checks value equality; `is` checks identity (same object). Use `is` for singletons like None.
How do you handle exceptions in Python?
Use try/except blocks; optionally with else and finally for cleanup. Avoid bare excepts.
What are Python's namespaces and scoping rules?
LEGB rule: Local, Enclosing, Global, Built-in. Variables are looked up in that order.
What is a generator and how is it different from a list?
Generators produce items lazily using yield; they don't store all values in memory, suitable for large data.
How do you optimize Python code for performance?
Use built-in functions (e.g., map, filter), list comprehensions, avoid global lookups, use profiling tools, and consider C extensions.
Practice Python questions with instant AI feedback
Upload your resume, get a personalized mock interview, and see exactly what to improve — free to start.