Interview Insights · Python
Why Python Eliminates More Candidates Than They Expect
Quick summary — Python round traps
Python does not eliminate candidates with hard syntax; it eliminates them with simple code that behaves unexpectedly. Because Python reads like plain English, candidates assume the code does what it looks like it does. Most of the time it does. But a handful of behaviours, such as list aliasing, modifying a list while looping over it and hidden linear-time operations, produce wrong or slow results in code that looks correct. Interviewers use these on purpose because they reveal whether you understand Python's model of objects and references. This page walks through the traps and how to prepare.
In this guide you will learn:
- Why the 'easy' Python round catches so many candidates
- List aliasing and copying traps, with code you can run
- Iteration bugs that look correct and give wrong answers
- Hidden complexity in innocent operations like in and +=
- A trap-spotting drill to run before your coding round
SECTION 01Aliasing: when two names share one object
In Python, variables are names attached to objects. Assigning one name to another does not copy anything. It attaches a second name to the same object. Most of the time this is invisible. It becomes visible, and costly, when the object is mutable and you change it through one name.
The classic interview version is building a grid. [[0] * 3] * 3 looks like a 3x3 grid of zeros. It is actually one inner list referenced three times. Change one cell and the whole column changes. Candidates who have never hit this bug usually give the wrong output with full confidence.
| Code | What candidates expect | What actually happens | Fix |
|---|---|---|---|
| grid = [[0] * 3] * 3 | Three independent rows | Three references to one row | [[0] * 3 for _ in range(3)] |
| b = a (lists) | b is a copy | b and a are the same list | b = a.copy() or list(a) |
| copy.copy(nested) | Full copy | Inner lists still shared | copy.deepcopy(nested) |
| dict.fromkeys(keys, []) | Each key gets its own list | All keys share one list | {k: [] for k in keys} |
grid = [[0] * 3] * 3
grid[0][0] = 9
print(grid)
# [[9, 0, 0], [9, 0, 0], [9, 0, 0]] - all rows changed!
print(grid[0] is grid[1]) # True - same object
# Correct: build a new inner list each time
grid = [[0] * 3 for _ in range(3)]
grid[0][0] = 9
print(grid)
# [[9, 0, 0], [0, 0, 0], [0, 0, 0]]
# Why [0] * 3 is fine but [[0]] * 3 is not:
# integers are immutable, so sharing them is harmless.
# Lists are mutable, so sharing them shares changes.import copy
teams = [["Asha", "Ravi"], ["Meena"]]
shallow = copy.copy(teams)
deep = copy.deepcopy(teams)
teams[0].append("Karan")
print(shallow[0]) # ['Asha', 'Ravi', 'Karan'] - inner list shared
print(deep[0]) # ['Asha', 'Ravi'] - fully independent
# Spoken: "A shallow copy makes a new outer list but
# keeps references to the same inner lists. A deep copy
# copies every level."
The explanation interviewers want to hear is the one in the comment: sharing immutable objects is harmless, sharing mutable ones shares changes. If you can say that, you can predict nearly every aliasing question you will be given.
SECTION 02Iteration bugs that look correct
The second big eliminator is changing a collection while you loop over it. It looks natural: loop over the numbers and remove the even ones. But a list loop works by index, so removing an item shifts everything left and the loop skips the next one. The result is wrong, and only sometimes, which makes it easy to miss in a quick test.
Dictionaries are stricter. Changing a dict's size during iteration raises a RuntimeError. That at least fails loudly. Lists fail quietly, which is why they appear in interviews more often.
| Pattern | Problem | Safe alternative | Why it is safe |
|---|---|---|---|
| Remove from list while looping | Skips elements after each removal | Build a new list with a comprehension | The original is not changed during the loop |
| Delete dict keys while looping | RuntimeError: dictionary changed size | Loop over list(d) or build a new dict | You iterate over a separate snapshot |
| Append to list while looping | Loop may never end | Collect new items, extend after | The loop's target stays fixed |
| Index-based removal forwards | Indexes shift after each delete | Loop backwards, or filter instead | Earlier indexes are unaffected |
nums = [2, 4, 5, 6, 8]
for n in nums:
if n % 2 == 0:
nums.remove(n)
print(nums)
# [4, 5, 8] - expected [5]
# What happened:
# index 0: 2 removed -> list is [4, 5, 6, 8], loop moves to index 1 (5)
# index 1: 5 kept
# index 2: 6 removed -> list is [4, 5, 8], loop moves to index 3
# index 3: does not exist -> loop ends
# 4 and 8 were never checked.nums = [2, 4, 5, 6, 8]
# 1. Comprehension (most Pythonic)
odds = [n for n in nums if n % 2 != 0]
print(odds) # [5]
# 2. In place, if other code holds a reference to nums
nums[:] = [n for n in nums if n % 2 != 0]
# 3. Dicts: iterate over a snapshot of the keys
stock = {"pen": 0, "book": 3, "bag": 0}
for item in list(stock):
if stock[item] == 0:
del stock[item]
print(stock) # {'book': 3}
Being able to trace the skip bug index by index, as in the first tab, is a strong signal. It shows that you understand how the loop actually works rather than just remembering that 'modifying while iterating is bad'.
SECTION 03Hidden complexity in innocent-looking code
The third eliminator is speed. Many coding rounds include a larger test case, or the interviewer asks 'what if the input had a million items?' Code that is fine on ten items can be far too slow at scale, and Python hides the cost well. x in my_list looks instant, but it scans the list. Put it inside a loop and you have quadratic time.
Candidates get eliminated here not because they do not know Big-O, but because they do not connect it to everyday Python operations. Knowing the cost of the common operations on lists, sets, dicts and strings is enough to avoid most of these traps.
| Operation | Cost | Faster choice | When it matters |
|---|---|---|---|
| x in list | Linear: scans items | x in set: average constant time | Membership checks inside loops |
| list.pop(0) or insert(0, x) | Linear: shifts every item | collections.deque.popleft() | Queues and BFS |
| s += piece in a loop | Can copy the string repeatedly | ''.join(pieces) | Building long strings |
| sorted() inside a loop | n log n each time | Sort once outside the loop, or use heapq | Repeated min or max lookups |
import time
n = 20_000
seen_list = list(range(n))
seen_set = set(seen_list)
queries = range(n)
start = time.perf_counter()
hits = sum(1 for q in queries if q in seen_list) # list scan each time
print("list:", round(time.perf_counter() - start, 3), "s")
start = time.perf_counter()
hits = sum(1 for q in queries if q in seen_set) # hash lookup
print("set: ", round(time.perf_counter() - start, 3), "s")
# Run it yourself: the set version is dramatically faster,
# and the gap grows as n grows.Q: Find all numbers that appear in both lists.
First version (say it, then improve it):
common = [x for x in a if x in b]
"This is O(len(a) * len(b)) because 'in b' scans b."
Improved:
b_set = set(b)
common = [x for x in a if x in b_set]
"Building the set is O(len(b)), each lookup is O(1)
on average, so total is O(len(a) + len(b)).
This keeps a's order and duplicates. If I only need
unique values, set(a) & set(b) is simpler."
Notice that the interview answer starts with the simple version and then improves it, saying the complexity of both. That is exactly what interviewers hope for. Nobody expects the optimal answer instantly. They expect you to notice the cost.
SECTION 04Trap-spotting drill before your Python round
Spend a few days on these sheets before your interview. The goal is to see the traps in code at a glance, so that you avoid them in your own solutions and can explain them if asked. In Uncodemy practice sessions, we use the same three-step approach: predict, run, explain.
PREDICT, THEN RUN
1. a = [1, 2]; b = a; b.append(3); print(a)
2. grid = [[""] * 2] * 2; grid[1][0] = "x"; print(grid)
3. d = dict.fromkeys(["a", "b"], []); d["a"].append(1); print(d)
4. nums = [1, 1, 2, 3]
for n in nums:
if n == 1: nums.remove(n)
print(nums)
5. t = (1, [2, 3]); t[1].append(4); print(t)
Answers:
1. [1, 2, 3]
2. [['x', ''], ['x', '']]
3. {'a': [1], 'b': [1]}
4. [1, 2, 3]
5. (1, [2, 3, 4]) - tuple cannot change, its list canCOST CHECK (say the Big-O aloud)
[ ] x in a list -> ____
[ ] x in a set -> ____
[ ] list.append(x) -> ____
[ ] list.insert(0, x) -> ____
[ ] deque.appendleft(x) -> ____
[ ] dict[key] -> ____
[ ] sorted(list) -> ____
[ ] ''.join(list_of_str) -> ____
Then review one of your old solutions and mark
every line that runs inside a loop.EDGE-CASE SWEEP (for every solution)
Test with:
[ ] empty input ([], "", {})
[ ] a single item
[ ] all items the same
[ ] negative numbers or zero
[ ] very large input (mentally: what is the cost?)
[ ] None where a value is expected
Say aloud: "For an empty list my function returns ___
because ___." If you cannot finish the sentence,
the edge case is not handled yet.
SECTION 05Habits that keep you out of Python's quiet traps
These habits make the traps visible before they cost you the round.
- Think in names and objects — When you assign, ask whether you just made a copy or a second name for the same object.
- Prefer building new collections — Comprehensions that build a new list avoid almost every modify-while-iterating bug.
- Use sets for membership — If you check 'in' repeatedly, convert to a set first and say why.
- Dry run with a tricky input — Choose an input with neighbours that both match, like [2, 4], to catch skip bugs.
- State complexity unprompted — Saying the time cost of your solution before being asked shows you thought about scale.
SECTION 06Interview Q&A — Python round traps
Q1Why does [[0] * 3] * 3 behave strangely?
The outer multiplication repeats a reference to the same inner list three times. All three rows are one object, so changing a cell in one row appears in every row. Use a comprehension, [[0] * 3 for _ in range(3)], to create a separate inner list for each row.
Q2What is the difference between a shallow and a deep copy?
A shallow copy creates a new outer container but keeps references to the same inner objects. A deep copy recursively copies everything, so nested objects are independent. Use copy.deepcopy when the structure is nested and you need full independence.
Q3Why should you not remove items from a list while iterating over it?
The for loop moves through the list by index. Removing an item shifts later items left, so the loop skips the element that moved into the current position. Build a new filtered list instead, or iterate over a copy.
Q4Why is x in a set faster than x in a list?
A set uses a hash table, so membership is checked by hashing x and looking at one bucket, which is constant time on average. A list has to compare x with each element in turn, which is linear time.
Q5Why use collections.deque for a queue?
Removing from the front of a list with pop(0) shifts every remaining element, which is linear time. A deque supports popleft and appendleft in constant time, making it the right choice for queues and breadth-first search.
SECTION 07Test yourself — Python round traps quiz
Five questions. No sign-up.
0 / 5Pick an answer to see why it is right or wrong.
SECTION 08Frequently asked questions
Why do people fail Python coding interviews?
Often not because of syntax, but because of behaviours that look harmless: shared references, modifying a list while looping, slow operations inside loops and untested edge cases. These produce wrong or slow results in code that looks right.
What are common Python interview traps?
List multiplication with nested lists, shallow copies, mutable values in dict.fromkeys, removing items while iterating, using in on large lists inside loops and building strings with += are some of the most common.
Do Python interviews ask about time complexity?
Yes, often. Interviewers expect you to know the cost of common operations on lists, sets, dicts and strings, and to explain how your solution scales to larger input.
How do I prepare for tricky Python output questions?
Practise predicting output before running code, especially for aliasing, copying and iteration. When you are wrong, write down the rule you missed and test it again with a new example.
Is Python easier than Java for coding interviews?
Python is shorter to write, which saves time. But interviewers adjust by asking about Python-specific behaviour, so you still need a solid grasp of how objects, references and data structures work.
How can I prepare for Python interview questions with a trainer?
Yes. Uncodemy's Python Training Course in Noida (classroom and online) includes live projects, one-to-one mock interviews and resume reviews, so you practise Python interview questions with trainer feedback before the real interview. You can book a free demo class from this page to see how the sessions work.
SECTION 09Related reads
Classroom & online · Noida
Learn Python deeply enough to avoid its traps
Our Python Training Course covers core Python, data structures, complexity and real projects, with coding rounds that include the traps interviewers actually use.
Talk to a counsellor for current fees- Core Python and data structures
- Output prediction drills
- Timed coding rounds
- Weekday & weekend batches