Interview Prep
Python Interview Questions — Basics
This lesson covers the fundamental behaviors and common pitfalls encountered during technical interviews in Python. Understanding these core concepts is vital for writing efficient, idiomatic, and bug-free production code. These basics provide the essential framework required to pass assessments and demonstrate depth in language design.
Variable Assignment and Mutability
Understanding how Python handles variables is the first step toward mastering the language. In Python, variables act as labels or names assigned to objects in memory rather than as storage containers. When you assign a value, you are binding a name to an object reference. This distinction is crucial when dealing with mutable and immutable types. Immutable objects, like strings, integers, or tuples, cannot be changed after creation. If you modify them, Python creates a new object in memory. Conversely, mutable objects like lists and dictionaries can be changed in place. Understanding this is essential for predicting side effects in functions; if you pass a mutable list to a function and modify it, the changes persist outside that scope because the function acts on the original object reference. Failure to grasp this leads to difficult-to-track state bugs in complex applications.
# Demonstration of mutability
def modify_list(data):
data.append(4) # Modifies the object in place
my_list = [1, 2, 3]
modify_list(my_list)
print(my_list) # Output: [1, 2, 3, 4]The Truthiness of Objects
Every object in Python has a built-in boolean value, which is evaluated during conditional checks like if or while statements. By default, most objects are considered 'truthy'. However, specific objects are defined as 'falsy', including None, False, empty sequences or collections like empty lists [], empty dictionaries {}, empty strings '', and the number zero. Understanding this allows you to write concise, idiomatic code without explicitly checking for lengths or nullity. When you write 'if my_list:', you are effectively asking if the list contains elements. This is preferred in professional environments because it focuses on the state of the object rather than its specific metadata. Relying on implicit truthiness makes your code cleaner and easier to read, as it minimizes redundant comparisons, but you must remain aware of what constitutes falsy values to avoid accidental bugs.
# Idiomatic truthiness checks
user_input = ''
if not user_input:
# This block executes because empty string is falsy
print('Input is missing.')List Comprehensions and Performance
List comprehensions provide a concise way to create lists, transforming or filtering existing iterables in a single line. They are not just syntactic sugar; they are often faster than standard for-loops because the iteration happens at the internal C level rather than through explicit Python bytecode instructions. When you construct a comprehension, you define an expression and an iterable, optionally adding a condition to filter elements. This paradigm is powerful for data processing pipelines. However, interviewers look for restraint; while they are efficient, nesting multiple comprehensions can lead to unreadable 'one-liners'. The goal is to balance brevity with maintainability. When your transformation logic becomes too complex, it is safer to revert to a traditional loop. Understanding this balance is key to showing you prioritize long-term code health over clever hacks that might confuse teammates during debugging cycles.
# Efficient transformation with filtering
numbers = [1, 2, 3, 4, 5, 6]
# Get squares of even numbers
squares = [x**2 for x in numbers if x % 2 == 0]
print(squares) # Output: [4, 16, 36]The Scope of Variables (LEGB Rule)
Python resolves variable names using the LEGB rule: Local, Enclosing, Global, and Built-in scope. When you reference a variable, the interpreter searches for it in this specific order. A local scope is created inside a function, while the enclosing scope refers to nested functions. Global scope applies to the module level, and the built-in scope covers core functions like print() or len(). This hierarchy is critical because it explains why you can read global variables inside a function but cannot modify them without the 'global' keyword. Using global variables inside functions is generally discouraged because it creates hidden dependencies and makes the code harder to test. Instead, you should pass data into functions as arguments and return results explicitly. This approach ensures your functions are pure and predictable, which significantly simplifies the debugging process during large-scale application development.
# LEGB scoping example
x = 10 # Global
def scope_demo():
x = 5 # Local, shadows global x
print(f'Local x: {x}')
scope_demo()
print(f'Global x: {x}')Exception Handling Best Practices
Exceptions in Python are objects that represent errors encountered during program execution. Instead of checking for return codes or manually validating every possible outcome, you use try, except, and finally blocks to manage flow control during errors. The 'try' block wraps the code that might trigger an issue, while the 'except' block defines the recovery path for specific error types. It is considered an antipattern to use a bare 'except:' clause, as this catches all errors, including system-level interruptions that should be allowed to propagate. Always catch specific exceptions to ensure you only handle the errors you expect. The 'finally' block is crucial for resource management, such as closing files or network sockets, as it executes regardless of whether an exception occurred. This ensures that your system resources are cleaned up properly, preventing memory leaks or locked files in production environments.
# Proper exception handling
try:
result = 10 / 0
except ZeroDivisionError:
print('Cannot divide by zero.')
finally:
print('Cleanup operations complete.')Key points
- Variables are names assigned to object references, not storage containers.
- Mutable objects like lists persist changes when passed to functions.
- Python evaluates empty collections and None as falsy in conditional checks.
- List comprehensions are generally faster due to internal C-level execution.
- The LEGB rule dictates how Python resolves variable names in different scopes.
- Using the 'global' keyword to modify state is generally discouraged.
- Always catch specific exceptions rather than using broad, bare except blocks.
- The finally block ensures resource cleanup regardless of success or failure.
Common mistakes
- Mistake: Using mutable objects like lists as default arguments. Why it's wrong: Default arguments are evaluated only once at definition time, so the list persists across calls. Fix: Use None as the default and initialize the list inside the function.
- Mistake: Misunderstanding global vs local scope when modifying variables. Why it's wrong: Python assumes a variable is local if it is assigned within a function, causing UnboundLocalError. Fix: Use the 'global' keyword if you must modify a global variable.
- Mistake: Modifying a list while iterating over it. Why it's wrong: It causes the iterator to skip elements or index incorrectly as the list size changes. Fix: Iterate over a copy of the list or use a list comprehension.
- Mistake: Confusing 'is' with '==' operators. Why it's wrong: 'is' checks for object identity (memory address), while '==' checks for value equality. Fix: Use '==' for comparing values and 'is' only for checking against None.
- Mistake: Assuming integers have a fixed maximum size. Why it's wrong: Python 3 handles arbitrarily large integers, but beginners often write unnecessary checks for overflow. Fix: Just perform the arithmetic operations directly.
Interview questions
What is Python and why is it considered a high-level programming language?
Python is an interpreted, object-oriented, high-level programming language known for its clear syntax and readability. It is considered high-level because it abstracts away complex computer hardware details like memory management and CPU registers. Instead, Python handles these tasks automatically through its garbage collector and virtual machine. This allows developers to focus on solving logic problems rather than managing system resources, making code more maintainable and expressive across different computing platforms.
How does memory management work in Python, and what is the role of the garbage collector?
Python manages memory through a private heap space that holds all objects and data structures. The programmer does not allocate memory manually; instead, Python’s memory manager handles it. The primary mechanism is reference counting, which tracks how many references point to an object. When the count drops to zero, the object is deallocated. Additionally, a cyclic garbage collector detects and cleans up reference cycles that simple counting cannot resolve, ensuring efficient memory usage.
Explain the difference between a list and a tuple in Python, and provide a scenario where one is preferable to the other.
Lists are mutable, meaning their contents can be changed, added, or removed after creation, while tuples are immutable, meaning they cannot be modified. You should use a list when you need a collection of items that may evolve during program execution. Conversely, tuples are better for fixed collections, such as coordinates or configuration settings. Tuples are also slightly more memory-efficient and faster to iterate over, providing a safeguard against accidental data mutation.
How do you explain the difference between a shallow copy and a deep copy in Python?
A shallow copy creates a new object, but inserts references into it to the objects found in the original. If you change a nested object in the copy, the change reflects in the original. A deep copy, however, creates a new object and recursively copies everything found in the original. Use a shallow copy when dealing with flat structures, but always use a deep copy via the copy module when your object contains nested mutable structures that need independent states.
Compare the use of List Comprehensions versus using standard for-loops for creating lists.
List comprehensions provide a concise way to create lists by condensing the logic of a standard for-loop into a single line. While both produce the same functional output, list comprehensions are generally faster because they are optimized for the interpreter to create the list in memory. However, you should choose a standard for-loop when the logic is complex, involves multiple steps, or requires error handling, as nested comprehensions often become unreadable and difficult to debug.
What are decorators in Python and how do they impact function execution?
Decorators are a powerful pattern that allows you to modify or extend the behavior of functions or methods without permanently changing their source code. A decorator is essentially a function that takes another function as an argument and returns a new wrapper function. For example: `def my_decorator(func): def wrapper(): print('Before'); func(); return wrapper`. By placing `@my_decorator` above a function, you encapsulate logic like logging, access control, or caching, promoting cleaner and more modular code design.
Check yourself
1. What is the result of the following code? def func(a=[]): a.append(1); return a; print(func()); print(func())
- A.[1], [1]
- B.[1], [1, 1]
- C.None, None
- D.Error: default arguments must be immutable
Show answer
B. [1], [1, 1]
Because the default argument 'a' is initialized when the function is defined, the same list object is reused in subsequent calls. Option 0 and 2 are incorrect because the list is modified; option 3 is incorrect because lists are allowed as default arguments, though it is considered bad practice.
2. Which of the following correctly describes how Python handles variable scope when using the 'global' keyword?
- A.It forces the variable to be accessible in all other modules.
- B.It allows a function to modify a variable defined at the module level.
- C.It makes the variable constant and immutable throughout the program.
- D.It allows a variable to be defined inside a function and seen by its parent function.
Show answer
B. It allows a function to modify a variable defined at the module level.
The 'global' keyword allows a function to perform assignments to a variable defined in the module-level scope. Option 0 is wrong as it doesn't cross module boundaries; option 2 is wrong because 'global' doesn't enforce constants; option 3 describes 'nonlocal'.
3. What is the difference between shallow copy and deep copy in the context of the copy module?
- A.Shallow copy is faster but uses more memory.
- B.Deep copy copies the object and all objects found within it recursively.
- C.Shallow copy creates a new object and populates it with references to the children of the original.
- D.Both 1 and 2 are correct.
Show answer
D. Both 1 and 2 are correct.
A shallow copy creates a new container but stores references to the original child objects, while a deep copy recursively copies everything. Option 0 is false as copy speed depends on complexity; option 3 correctly identifies both fundamental definitions.
4. What is the primary behavior of the 'is' operator in Python?
- A.It checks if two variables refer to the exact same object in memory.
- B.It compares the values of two objects for equality.
- C.It checks if an object is an instance of a specific class.
- D.It performs a bitwise comparison between two integers.
Show answer
A. It checks if two variables refer to the exact same object in memory.
'is' checks for identity, meaning the objects share the same memory address. '==' checks for value equality. Option 2 describes 'isinstance()', and option 3 is unrelated.
5. If you have a list 'nums = [1, 2, 3]', what happens if you run 'for x in nums: nums.remove(x)'?
- A.It removes all items and leaves an empty list.
- B.It raises a ValueError.
- C.It removes only some items because the index pointer increments while the list shrinks.
- D.The loop continues infinitely.
Show answer
C. It removes only some items because the index pointer increments while the list shrinks.
Modifying a list during iteration causes the loop to skip over elements because the indices shift. Option 0 is false because not all elements are processed; option 1 is false because the code is syntactically valid; option 3 is false as the loop will eventually exhaust.