Mutable defaults, list versus tuple, is versus ==, generators and more. Clear explanations of the Python questions interviewers love, with short code you can run.
Python interviews are rarely about obscure syntax. They check whether you understand how the language behaves, especially in the places where it surprises people. If you only memorize an answer, the first follow-up question sinks you. If you understand the reason, you can work out the next answer on the spot.
Here are six questions that appear again and again, and the thinking behind each one.
1. What is the difference between a list and a tuple?
Both hold an ordered collection. The key difference is that a list is mutable (you can change it after creating it) and a tuple is immutable (you cannot).
| List | Tuple | |
|---|---|---|
| Syntax | [1, 2, 3] | (1, 2, 3) |
| Can change items | Yes | No |
| Can be a dict key | No | Yes, if its items are hashable |
| Typical use | A collection that grows or changes | A fixed record, like (x, y) |
Follow-up you may get: “Why would you choose a tuple?” Because it signals the data should not change, it can be used as a dictionary key, and it is slightly lighter in memory.
2. What is wrong with a mutable default argument?
This is the most famous Python trap. Default values are created once, when the function is defined, not each time it is called. If the default is a list, every call shares the same list.
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("a")) # ['a']
print(add_item("b")) # ['a', 'b'] <- surprise, the list was reusedThe fix is to use None as the default and create a fresh list inside the function:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items3. What is the difference between “is” and “==”?
== asks “do these two have the same value?” is asks “are these the exact same object in memory?”
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True, same contents
print(a is b) # False, two separate lists
print(a is a) # TrueUse == for comparing values. Use is almost only for None, as in if x is None. Interviewers like to ask about small integers and strings being reused by Python. The honest answer is that this is an implementation detail you should never depend on.
4. What is a generator and why use one?
A generator produces values one at a time, on demand, instead of building the whole collection up front. That saves memory when the data is large.
# builds all one million numbers in memory first
numbers = [n * n for n in range(1_000_000)]
# produces one number at a time as you loop
numbers = (n * n for n in range(1_000_000))
def read_lines(path):
with open(path) as f:
for line in f:
yield line.strip()5. What is a decorator?
A decorator is a function that takes another function and returns an improved version of it. It lets you add behavior like logging or timing without editing the original.
import time
def timed(fn):
def wrapper(*args, **kwargs):
start = time.time()
result = fn(*args, **kwargs)
print(fn.__name__, "took", round(time.time() - start, 3), "s")
return result
return wrapper
@timed
def slow_add(a, b):
time.sleep(0.5)
return a + b
slow_add(2, 3) # prints: slow_add took 0.5 sThe @timed line is shorthand for slow_add = timed(slow_add). If you can say that sentence, you understand decorators.
6. What is the difference between a shallow and a deep copy?
A shallow copy makes a new outer container, but the items inside are still the same objects. A deep copy copies everything, all the way down.
import copy
original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)
original[0].append(99)
print(shallow[0]) # [1, 2, 99] inner list is shared
print(deep[0]) # [1, 2] fully independentHow to practice these
- Type each example yourself and change it. Predict the output first.
- Explain each answer out loud in two sentences, as if to a teammate.
- For every answer, ask yourself “when would this bite me in real code?” That is the follow-up question.