Python idioms for interviews¶
At a glance
Priority: P0 · Complexity: ⅕ · Est. time: 1 h · Phase: 0 · Prereqs: Approach & complexity
You're done when: you can write BFS, a heap-based top-k, binary search with bisect, and memoised DFS from memory with no reference, and name the complexity of every stdlib call you used.
Why it matters¶
Python is the fastest interview language when you use its standard library fluently: collections, heapq, bisect, functools, itertools. It is a liability when you don't, because hidden O(n) calls, recursion limits and mutable-default bugs cost time. You already write production Python. This page is the interview-specific subset: the 30 idioms that save 5–10 minutes per round, plus the traps.
Core concepts¶
The interview toolbox¶
| Need | Idiom | Complexity | Note |
|---|---|---|---|
| Frequency count | Counter(s), cnt.most_common(k) |
O(n), O(n log k) | Counter subtraction drops non-positive counts |
| Default values | defaultdict(list), defaultdict(int) |
O(1) | Graph adjacency: g = defaultdict(list) |
| Queue / deque | deque(), append, popleft, appendleft |
O(1) | Never use list.pop(0) (O(n)) |
| Min-heap | heapq.heappush(h, x), heappop(h), heapify(a) |
O(log n), O(n) heapify | Max-heap: push -x. Tie-break with tuples (prio, idx, obj) |
| Top-k | heapq.nlargest(k, it, key=) |
O(n log k) | Fine for one-shot use |
| Sorted search | bisect_left(a, x), bisect_right, insort |
O(log n), insort O(n) | key= supported since 3.10 |
| Memoisation | @functools.cache (3.9+) / @lru_cache(None) |
per-call O(1) lookup | Args must be hashable, so convert lists to tuples |
| Sorting | a.sort(key=lambda x: (x[1], -x[0])) |
O(n log n), stable | Custom comparator: functools.cmp_to_key |
| Combinatorics | permutations, combinations, product, accumulate, pairwise (3.10+) |
output-sized | accumulate gives prefix sums |
| Infinity | float('inf'), math.inf |
||
| Integer tricks | divmod, // floors toward -inf, int(a / b) truncates |
RPN division needs truncation | |
| Bits | x.bit_count() (3.10+), x & -x lowest bit, x.bit_length() |
O(1)-ish | Python ints are arbitrary precision, so mask with & 0xFFFFFFFF |
| Grid neighbours | for dr, dc in ((1,0),(-1,0),(0,1),(0,-1)): |
Bounds check 0 <= r < R |
|
| Swap | a, b = b, a |
||
| Enumerate/zip | for i, (x, y) in enumerate(zip(a, b)): |
zip(strict=True) in 3.10+ |
|
| Sorted container | sortedcontainers.SortedList |
O(log n) | Not stdlib. Available on LeetCode; ask before using in other environments |
Reusable templates¶
from collections import deque
def bfs(start, neighbours):
q = deque([start])
seen = {start}
dist = 0
while q:
for _ in range(len(q)): # process one level
node = q.popleft()
# if node is target: return dist
for nxt in neighbours(node):
if nxt not in seen:
seen.add(nxt) # mark on push, not on pop
q.append(nxt)
dist += 1
return -1
Traps that cost interviews¶
| Trap | Symptom | Fix |
|---|---|---|
[[0] * m] * n |
All rows alias the same list | [[0] * m for _ in range(n)] |
Mutable default arg def f(x, acc=[]) |
State leaks between calls | acc=None |
| Recursion depth > 1000 | RecursionError on deep DFS |
Iterative stack or sys.setrecursionlimit |
Appending path itself in backtracking |
All results identical and empty | res.append(path[:]) |
heapq with un-comparable objects |
TypeError on tie |
(priority, counter, obj) |
-7 // 2 == -4 |
Wrong truncation in RPN or digit math | int(-7 / 2) == -3 |
| String building in a loop | Quadratic time | List plus "".join |
@cache on a method with self or list args |
Unhashable, or memory leak | Nested function or tuple args |
| Modifying a dict while iterating | RuntimeError |
Iterate list(d.items()) |
Senior-level nuance¶
- Readability is scored. Prefer
for i, x in enumerate(a)to index gymnastics. Extract helpers (in_bounds(r, c),neighbours(node)). - Know when not to use a one-liner.
sorted(Counter(a).items(), key=...)[:k]is fine, but explain the O(n log n) cost versus a heap or bucket sort. - Python 3.14 free-threaded builds (PEP 779, officially supported as of Sept 2026) change the concurrency story. See concurrency & LLD. For DSA rounds, nothing changes.
- Type hints are optional in interviews. Use them for function signatures only if it helps readability.
Resources¶
| Resource | Type | Why this one | Level | Cost |
|---|---|---|---|---|
| collections docs | docs | deque, Counter, defaultdict, OrderedDict APIs (official) |
intermediate | free |
| heapq docs | docs | Includes the priority-queue implementation notes with entry-counter tie-breaks | intermediate | free |
| bisect docs | docs | key= parameter and search recipes |
intermediate | free |
| functools docs | docs | cache, lru_cache, cmp_to_key |
intermediate | free |
| itertools docs | docs | Combinatoric iterators and accumulate/pairwise |
intermediate | free |
| Python Sorting HOWTO | docs | Key functions, stability, multi-key sorts | intermediate | free |
| Python TimeComplexity wiki | docs | Cost of every built-in container operation | intermediate | free |
| sortedcontainers |
docs | Pure-Python sorted list/dict available on LeetCode, great for interval and calendar problems | advanced | free |
| Fluent Python 2e | book | Data model depth; the chapters on sequences and dicts explain the costs above | advanced | paid |
Hands-on lab¶
Goal: muscle memory (45 min). With no references, in a blank file:
- Write BFS shortest path on a grid with obstacles (
0open,1wall). Test on a 5×5 grid. - Write top-k frequent words using
Counter+heapqwith tie-break by lexicographic order (LC 692 style). - Write
lower_boundby hand, then verify againstbisect_lefton 1,000 random arrays withassert. - Write memoised
coin_change(coins, amount)with@cache, then convert it to a bottom-up table. - Run
python -X importtime(optional) andtimeitto comparelist.pop(0)withdeque.popleft()for n=10^5 and record the numbers.
Expected output: idioms.py with all five passing asserts, committed to your practice repo, plus a note on the timing comparison (expect orders of magnitude difference).
Questions¶
L1 — Recall¶
Q1. How do you implement a max-heap with heapq?
Answer
Push negated keys: heappush(h, -x) and read -h[0]. For tuples: (-priority, tiebreak, item). For objects, define __lt__ or wrap them in a dataclass with order=True.
Q2. What is the difference between bisect_left and bisect_right?
Answer
bisect_left(a, x) returns the first index i with a[i] >= x (lower bound). bisect_right returns the first index with a[i] > x (upper bound). With duplicates, right - left is the count of x.
Q3. Why is deque preferred over list for BFS?
Answer
deque.popleft() is O(1). list.pop(0) shifts all elements, which is O(n) and turns BFS into O(V^2).
L2 — Apply¶
Q4. Group anagrams in one idiomatic function. State its complexity.
Answer
O(N·K) time for N strings of max length K (versus O(N·K log K) with a sorted-string key). O(N·K) space.Q5. Fix this backtracking bug.
def subsets(nums):
res, path = [], []
def bt(i):
if i == len(nums):
res.append(path); return
path.append(nums[i]); bt(i + 1); path.pop(); bt(i + 1)
bt(0); return res
Answer
res.append(path) stores a reference to the same list, which is empty at the end. Use res.append(path[:]) (or list(path)).
Q6. Sort intervals by start ascending, ties by end descending, in one line.
Answer
intervals.sort(key=lambda iv: (iv[0], -iv[1])). Python's sort is stable, so you could also do two passes (secondary key first).
L3 — Design & trade-offs¶
Q7. @cache vs an explicit dp table: which do you write first in an interview?
Answer
Top-down @cache first. It mirrors the recurrence, is fast to write, and computes only reachable states. Risks: recursion depth (n > ~1000) and memory held by the cache. Then offer bottom-up if asked about space optimisation (rolling arrays) or recursion limits. Some interviewers ask you to avoid @cache to see the table, so be fluent in both.
Q8. When would you use sortedcontainers.SortedList and what do you say to the interviewer?
Answer
Use it when you need ordered insert/delete plus rank/neighbour queries: sliding window median, calendar booking, "count smaller after self". Say: "Python has no built-in balanced BST. SortedList gives O(log n) insert/remove (amortised; it's a list of sorted sublists). If it's not allowed, I'd use two heaps with lazy deletion or a Fenwick tree over compressed coordinates." Ask permission first.
Q9. Recursion vs an explicit stack for tree DFS in Python?
Answer
Recursion is clearer and fine when height ≤ ~1000 (balanced trees, typical LC constraints where n ≤ 10^4 but trees may be skewed, so check). An explicit stack avoids RecursionError and is required for post-order variants on deep trees. Say which you picked and why.
L4 — Staff-level ambiguity¶
Q10. Your team debates banning @lru_cache on instance methods in production code. What's your position?
Answer
Support the ban with an exception process. lru_cache on a method keys on self, which keeps instances alive (a memory leak) and shares one cache across instances with confusing eviction. Alternatives: functools.cached_property for per-instance values, a module-level cache keyed on explicit ids, or an injected cache object with TTL and metrics. The general principle: memoisation is a data structure with a lifecycle, so make it explicit and observable.
Q11. The interviewer says 'no libraries: implement the heap yourself'. How do you manage time?
Answer
Confirm the scope (just push/pop?). Write an array-backed binary heap with _sift_up / _sift_down (about 20 lines, 5–7 minutes). Parent (i-1)//2, children 2i+1, 2i+2. Test with a quick sequence. Mention that heapify is O(n) by sifting down from n//2 - 1 and explain why (the sum of heights is O(n)).
Real-world use cases¶
- Job schedulers:
heapqwith(run_at, seq, job)tuples is exactly how simple in-process schedulers (andsched) work. - Rate limiting: a
dequeof timestamps for sliding-window-log limiters in API gateways. - Analytics:
Counter.most_commonfor top error codes,bisectfor bucketing latencies into histogram bins. - Config/rule engines:
defaultdict(list)adjacency lists for rule dependency graphs, with topological order for evaluation.
Pitfalls & anti-patterns¶
- Reaching for
sortedcontainersornumpywithout asking. - Using
listas a queue, orin listfor membership. - Returning references to mutable accumulators in backtracking.
- Writing heavy OOP (classes for everything) when a function is enough.
- Relying on dict ordering for correctness without saying so (insertion order has been guaranteed since 3.7, so it's fine, but state it).
Checklist¶
- I can write BFS, top-k heap, lower_bound and memoised DFS without references
- I know the complexity of every
collections,heapqandbisectcall I use - I can list five Python traps and their fixes
- I answered all L3 questions out loud in < 3 min each