Interview Prep — Python Track
Python Interview Questions
15 curated Python questions — mutability, decorators, generators, the GIL, OOP, and async. Tagged for FAANG and startups. Check off as you go.
BasicsAdvancedOOPLibraries
30Total
10Basics
10Advanced
5OOP
5Libraries
01
What are Python's mutable and immutable data types?
Python
▾
Immutable:
int, float, str, tuple, frozenset, bool — cannot be changed after creation. Changing creates a new object.
Mutable: list, dict, set — can be modified in place.
Mutable: list, dict, set — can be modified in place.
Why it matters:
Immutable objects are hashable (usable as dict keys). Mutable default function arguments are a common bug — use
None
as default instead.
python
# Bug: mutable default argument def bad(lst=[]): lst.append(1); return lst # Fix: def good(lst=None): if lst is None: lst = [] lst.append(1); return lst
02
What is the difference between
append() and extend()?
Python
▾
append adds its argument as one element; extend iterates it and adds each item. a = [1, 2] a.append([3, 4]) # [1, 2, [3, 4]] b = [1, 2] b.extend([3, 4]) # [1, 2, 3, 4] b += [5] # same as extend
03
Why is a mutable default argument a bug?
Python
▾
Defaults are evaluated once, at definition time, so every call shares one object.
def add(item, bucket=[]): # created once
bucket.append(item)
return bucket
add(1) # [1]
add(2) # [1, 2] — not a fresh list
def add(item, bucket=None): # the fix
if bucket is None:
bucket = []
04
How does slicing work, and how is it different from
split()?
Python
▾
Slicing takes a range by position from any sequence and returns the same type;
split() is a string method that cuts on a separator and returns a list. s = "hello world"
s[0:5] # "hello" — by position
s[::-1] # reversed
s[-5:] # "world" — negative indexes count from the end
s.split(" ") # ["hello", "world"] — by separator, gives a list
nums[::2] # every second element — slicing works on lists tooA slice never raises for an out-of-range bound; it just returns fewer items, which is why s[:100] is safe.
05
How do you iterate with an index, or over two sequences at once?
Python
▾
enumerate for the index, zip for parallel iteration. Both are lazy, so neither builds an intermediate list. for i, name in enumerate(names, start=1): # start= is the useful half
print(i, name)
for name, score in zip(names, scores):
print(name, score)
# zip stops at the shortest input; zip(..., strict=True) raises instead (3.10+)
06
What does
if __name__ == "__main__": do?
Python
▾
Python sets
__name__ to "__main__" in the file it was told to run, and to the module name in anything imported. The guard therefore runs a block only on direct execution — which is what stops a script's side effects from firing when a test runner or another module imports it.
07
Explain Python decorators with an example.
Python
▾
A
decorator
is a function that wraps another function to add behaviour without modifying its code — uses the
higher-order function
concept.
Common uses:
logging, authentication, caching (
python
import time def timer(func): def wrapper(*args, **kwargs): t = time.time() result = func(*args, **kwargs) print(f"Took {time.time()-t:.3f}s") return result return wrapper @timer def slow_fn(): time.sleep(0.5)
@functools.lru_cache
), rate limiting.
08
What are generators in Python? How are they different from lists?
Python
▾
Generators
are functions that yield values one at a time using
yield
, producing values lazily (on demand) instead of storing the whole sequence in memory.
python
def fibonacci(): a, b = 0, 1 while True: yield a a, b = b, a + b gen = fibonacci() print([next(gen) for _ in range(8)]) # [0,1,1,2,3,5,8,13]
Memory:
Generator uses O(1) space regardless of sequence length. A list would use O(n). Perfect for large datasets, file reading, streaming.
09
What is the GIL in Python and how does it affect multithreading?
Python
▾
The
GIL (Global Interpreter Lock)
is a mutex in CPython that allows only one thread to execute Python bytecode at a time, even on multi-core CPUs.
- Problem: CPU-bound multithreaded code doesn't benefit from multiple cores
- Not a problem for I/O-bound tasks — GIL is released during I/O operations (network, file, sleep)
-
Solution for CPU-bound:
Use
multiprocessing(separate processes, each with own GIL) or C extensions
Python 3.13+:
Experimental "free-threaded" mode can disable the GIL.
10
What is the difference between an iterator and an iterable?
Python
▾
An iterable can produce an iterator (
__iter__). An iterator produces values one at a time (__next__) and raises StopIteration when exhausted. A list is iterable but not an iterator. Iterators are single-use — which is why a generator you have already looped over yields nothing the second time.
11
What is pickling, and when should you not use it?
Python
▾
pickle serialises a Python object graph to bytes and back, preserving types that JSON cannot. import pickle
with open("model.pkl", "wb") as fh:
pickle.dump(obj, fh)Never unpickle data you did not produce — the format can execute arbitrary code on load, so it is a remote code execution hole, not merely a parsing risk. It is also Python-specific and version-fragile: for anything crossing a language, a network or a long time span, use JSON or a schema format.
12
What does
functools.lru_cache do, and when is it wrong?
Python
▾
It memoises a function on its arguments, so a repeat call is a dict lookup. It is wrong when the function is not pure (you cache a stale answer), when arguments are unhashable, or when the key space is unbounded — an uncapped cache keyed on user input is a memory leak. Set
maxsize, and note the cache holds strong references to both arguments and results.
13
How does exception chaining work?
Python
▾
Raising inside an
except block chains implicitly: the traceback shows both, joined by "During handling of the above exception". raise NewError() from err states the cause explicitly, and from None suppresses it. It matters because the wrapper alone rarely explains the failure — the original cause is the useful half.
14
How do you debug and profile a Python script?
Python
▾
For debugging,
breakpoint() drops into pdb at that line (n next, s step, c continue, p expr print) — no import needed since 3.7. For profiling, start coarse and narrow down. python -m cProfile -s cumtime script.py # where the time goes, by function python -m timeit -s "setup" "statement" # micro-benchmark one lineProfile before optimising: the slow line is rarely the one you would have guessed, and
cumtime points at the caller responsible rather than the leaf it bottoms out in.
15
Explain list comprehension vs map/filter vs for loop — when to use what?
Python
▾
python
nums = [1,2,3,4,5] # List comprehension — Pythonic, readable, fast evens = [x for x in nums if x%2==0] # map/filter — functional, lazy, returns iterators evens = list(filter(lambda x: x%2==0, nums)) # for loop — most readable for complex logic evens = [] for x in nums: if x%2==0: evens.append(x)
for
loops for complex logic. Use
map
/
filter
when chaining with itertools or for lazy evaluation.
16
What are *args and **kwargs in Python?
Python
▾
*args
collects extra positional arguments as a
tuple
.
**kwargs
collects extra keyword arguments as a
dict
.
python
def greet(name, *args, **kwargs): print(f"Hello {name}") print(f"Extra args: {args}") print(f"Extra kwargs: {kwargs}") greet("Alice", 25, "NYC", city="Boston", active=True)
17
What is the difference between deep copy and shallow copy?
Python
▾
Shallow copy
creates a new object but inserts references to the original nested objects.
Deep copy creates a new object and recursively copies all nested objects.
Deep copy creates a new object and recursively copies all nested objects.
python
import copy lst = [[1, 2], [3, 4]] shallow = copy.copy(lst) deep = copy.deepcopy(lst) lst[0][0] = 999 print(shallow) # [[999, 2], [3, 4]] - affected! print(deep) # [[1, 2], [3, 4]] - independent
18
Explain the difference between == and is in Python.
Python
▾
==
checks
value equality
(do they have the same content?).
is
checks
reference equality
(are they the same object in memory?).
python
a = [1, 2, 3] b = [1, 2, 3] c = a print(a == b) # True (same values) print(a is b) # False (different objects) print(a is c) # True (same reference)
19
What is the difference between list and tuple? When would you use each?
Python
▾
| Aspect | List | Tuple |
|---|---|---|
| Mutability | Mutable | Immutable |
| Syntax | [1, 2, 3] |
(1, 2, 3) |
| Performance | Slower | Faster |
| Use case | Dynamic data | Fixed records, dict keys |
20
When do you reach for
defaultdict, Counter and deque?
Python
▾
defaultdict when every missing key should start from a known empty value, replacing the
setdefault dance. Counter for tallying, with most_common(n) free. deque when you push and pop at both ends — O(1) at the front, where a list is O(n) because it shifts every element.
21
What does
pathlib give you over os.path?
Python
▾
Paths as objects rather than strings, so joining is an operator and the operations are methods on the thing itself.
from pathlib import Path
p = Path("data") / "raw" / "input.csv" # right separator on every OS
p.suffix, p.stem, p.parent
p.exists(), p.read_text(), p.with_suffix(".json")
for f in Path("logs").rglob("*.log"): ...
22
What are context managers and the 'with' statement?
Python
▾
Context managers handle resource setup and teardown automatically using
__enter__
and
__exit__
methods.
python
# Using with statement with open("file.txt") as f: data = f.read() # File automatically closed # Custom context manager from contextlib import contextmanager @contextmanager def timer(): import time start = time.time() try: yield finally: print(f"Took {time.time()-start:.3f}s")
23
What is the difference between @classmethod, @staticmethod, and instance method?
Python
▾
python
class MyClass: count = 0 def instance_method(self): # access self & class return self @classmethod def class_method(cls): # access class, not instance return cls.count @staticmethod def static_method(x): # no self or cls return x * 2
24
What are dunder (magic) methods in Python?
Python
▾
Dunder methods
(double underscore) let you define how objects behave with built-in operators and functions.
-
__init__— constructor -
__str__,__repr__— string representation -
__len__—len(obj) -
__eq__,__lt__,__gt__— comparison operators -
__add__,__mul__— arithmetic operators -
__getitem__,__setitem__— indexingobj[key] -
__iter__,__next__— make object iterable -
__enter__,__exit__— context manager protocol
25
Explain inheritance and the MRO (Method Resolution Order) in Python.
Python
▾
Python uses
C3 linearization
to determine MRO in multiple inheritance.
Use
python
class A: pass class B(A): pass class C(A): pass class D(B, C): pass print(D.__mro__) # D → B → C → A → object
help(Class)
or
Class.__mro__
to inspect the order.
26
What is a dataclass, and when do you still write the class by hand?
Python
▾
@dataclass generates __init__, __repr__ and __eq__ from annotated fields, with frozen=True giving immutability and a working __hash__. Write it by hand when construction needs real logic, when the class is mostly behaviour rather than data, or when the fields should stay private. For a value object it is the right default.
27
What does
super() actually resolve to?
Python
▾
Not "my parent" — "the next class in this instance's MRO", which depends on the object's runtime type rather than where the call is written. That is what makes cooperative multiple inheritance work: every class calls
super(), each runs once, in the linearised order you can read from Class.__mro__.
28
What is the difference between range() and xrange()?
Python
▾
Python 2:
-
range()returns a list -
xrange()returns a generator (lazy evaluation)
-
range()behaves like xrange() (returns a range object, lazy) -
xrange()doesn't exist
Tip:
In Python 3, range() is memory-efficient regardless of size.
29
How do you handle exceptions in Python? Explain try, except, else, finally.
Python
▾
python
try: result = 10 / 0 except ZeroDivisionError: print("Cannot divide by zero") else: print(f"Result: {result}") # runs if no exception finally: print("Cleanup complete") # always runs
- try: Code that might raise an exception
- except: Handle specific exceptions
- else: Runs if no exception occurred
- finally: Always executes (cleanup code)
30
Find the maximum profit by buying and selling stock at different times.
Python
▾
Problem: Given stock prices with timestamps, find the maximum profit by buying at one price and selling at a later higher price.
Explanation:
python
prices = [
("2026-03-29 09:30:00", 150.25),
("2026-03-29 09:31:00", 152.10),
("2026-03-29 09:32:00", 151.80),
("2026-03-29 09:33:00", 154.50)
]
max_profit = 0
min_price = prices[0][1]
for time, price in prices[1:]:
profit = price - min_price
max_profit = max(max_profit, profit)
min_price = min(min_price, price)
print(f"{max_profit:.2f}") # 4.25
- Track running minimum price seen so far
- Calculate profit if selling at current price
- Update max_profit and min_price as we iterate
- Time: O(n) — single pass through the array
- Space: O(1) — only two variables tracked
Pattern: This is the classic "Best Time to Buy and Sell Stock" interview problem. The key insight is that you must buy before you sell, so we track the minimum price seen so far and calculate potential profit at each step.
No questions match.