1 · Python fluency for interviews Code
Collections, idioms, built-ins and what each operation costs · the base for every coding question
Why it matters for NPE. The Meta guide grades syntax/language familiarity, structure, bugs and efficiency. In a 45-minute CoderPad round you cannot afford to look up how Counter or sorted(key=) works.
Primer: what "language fluency" means to the grader
The guide says the interviewer scores efficiency, structure, syntax/language familiarity, bugs and correctness. In practice that means: you pick the right container the moment you hear the problem, you write it without pausing to remember an argument order, and you can say the cost of every line. This page is the toolbox. Everything later builds on it.
Watch
Watch all of it once. Then rewatch 8:40 arrays, 12:38 sorting, 15:25 strings, 16:50 queues, 17:30 hash sets, 18:25 hash maps and 20:55 heaps until you can type each snippet from memory.
Only if list/tuple/set still feel interchangeable. Slower and more thorough than NeetCode.
get, in, items(), deleting keys. Ten minutes, worth it if you've mostly used JavaScript objects.
sorted(key=…), reverse=True, sorting by several fields with a tuple key. You will use this in half the problems.
The cost side: why append is amortised O(1), insert(0) is O(n), and string concatenation in a loop is quadratic (11:15 strings, 13:30 Python code).
The containers and what each operation costs
| Container | Use it when | O(1) | Not O(1) |
|---|---|---|---|
list | Ordered sequence, index access, stack (append/pop()) | a[i], append, pop() (end), len | x in a O(n), insert(0,x) and pop(0) O(n), sort O(n log n) |
tuple | Immutable record; hashable, so usable as a dict key or set member | same as list for reads | cannot change; build a new one |
set | Membership, dedup, intersection/difference | add, remove/discard, x in s (expected) | no order, no indexing; a & b O(min(len)) |
dict | Key → value; counting, grouping, caching, lookup tables | d[k], d.get(k, default), k in d, assignment, del (expected) | iteration O(n); keys must be hashable (no lists) |
collections.deque | Queue (BFS), sliding window of recent items | append, appendleft, popleft, pop | indexing in the middle O(n) |
heapq on a list | Repeatedly need the smallest (or largest, negate) item; top-k | heap[0] peek | heappush/heappop O(log n), heapify O(n) |
Counter | Frequency map in one line; most_common(k) | same as dict | most_common(k) O(n log k) |
defaultdict(list|int|set) | Group-by without if key not in d | same as dict | reading a missing key creates it (trap below) |
Dict and set costs are expected O(1) because of hashing. If asked "worst case?", say O(n) with pathological collisions, then move on. Python 3.7+ dicts keep insertion order; sets do not.
Idioms you must type without thinking
# iterate with index, or two sequences in lockstep
for i, host in enumerate(hosts): # i starts at 0; enumerate(hosts, 1) starts at 1
...
for host, status in zip(hosts, statuses): # stops at the shorter one
...
# counting and grouping
from collections import Counter, defaultdict, deque
counts = Counter(words) # counts['x'] is 0 for missing keys, no KeyError
counts.most_common(3) # [(word, n), ...] highest first
groups = defaultdict(list)
for rec in records:
groups[rec.region].append(rec)
seen = {}
seen[key] = seen.get(key, 0) + 1 # the manual version of Counter
# sorting
sorted(items, key=lambda x: (-x[1], x[0])) # count descending, then name ascending
items.sort(key=len) # in place, returns None (trap!)
sorted(d.items(), key=lambda kv: kv[1]) # dict by value
# strings
parts = line.strip().split(',') # strip() first or the last field keeps '\n'
line.split() # no argument: any whitespace, drops empties
','.join(fields) # fields must all be str
s.lower(), s.isdigit(), s.isalnum(), s.startswith('web-')
f"{host}: {count} failures"
# comprehensions and unpacking
squares = [x * x for x in nums if x % 2 == 0]
lookup = {h.lower(): s for h, s in pairs}
first, *rest = values
a, b = b, a
# truthiness and defaults
if not items: # empty list/dict/set/str and 0 and None are all falsy
...
value = d.get(k) or 'unknown' # careful: 0 and '' are also "or"-replaced
Mutability: where bugs come from
| Mutable (change in place) | Immutable (every "change" is a new object) |
|---|---|
list, dict, set, deque, your classes | int, float, str, tuple, frozenset, bool, None |
- Aliasing.
b = adoes not copy a list.b.append(1)changesatoo. Copy witha[:],list(a)ora.copy()(shallow). - Mutable default argument.
def f(x, acc=[])shares one list across calls. Useacc=Nonethenacc = acc or []. - Grid aliasing.
[[0] * cols] * rowsmakes one row repeated. Use[[0] * cols for _ in range(rows)]. - In-place methods return None.
x = lst.sort(),x = lst.reverse(),x = lst.append(v)all setxtoNone. Usesorted()/reversed()when you want a value. - Modifying while iterating. Deleting dict keys inside
for k in draises. Iterate overlist(d)or build a new dict. - Strings are immutable.
s += chin a loop is O(n²) in principle. Collect in a list and''.join. isvs==. Useisonly forNone.==compares values.
Reading and writing files (preview of topic 5)
with open(path, encoding='utf-8') as f: # closes the file even on exceptions
for line in f: # streams one line at a time: O(1) memory per line
line = line.rstrip('\n')
if not line:
continue
Never f.read() or f.readlines() a file you were told might be large. The streaming loop is the answer to "what if the file is 20 GB?"
Big-O in one breath
State the cost in terms of the inputs the interviewer named: n lines, u distinct hosts, k results. "O(n) time to scan the file, O(u) space for the counts, plus O(u log k) to pick the top k with a heap." Common shapes:
| Shape | Typical code |
|---|---|
| O(1) | dict/set lookup, list index, append/pop at the end |
| O(log n) | binary search, one heap push/pop |
| O(n) | one pass over the input, Counter(), building a set |
| O(n log n) | sorting the whole input |
| O(n log k) | keeping a size-k heap while scanning n items |
| O(n²) | nested loops over the same input, x in list inside a loop, repeated insert(0) |
Nested loops are not automatically O(n²): a loop over rows and then over that row's cells is O(total cells).
Interview questions
1. Why is a dict lookup O(1)? When isn't it?
Keys are hashed to a slot in an array, so finding a key costs the hash plus a probe or two. With many colliding keys the probe chain grows, so the worst case is O(n). In practice say "expected O(1)".2. list vs tuple: when would you choose a tuple?
When the record should not change, or when it has to be a dict key or set member (lists are unhashable). Also for returning several values from a function.3. What does lst.sort() return? And sorted(lst)?
lst.sort() sorts in place and returns None. sorted(lst) returns a new sorted list and works on any iterable (dict keys, sets, generators).4. Sort hosts by failure count descending, then by name ascending.
sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])). Negate the number for descending; the string sorts ascending by default. Python's sort is stable, so equal keys keep their input order.5. Why is pop(0) on a list a problem for a queue?
Every element shifts left, so it is O(n). Use collections.deque with popleft(), which is O(1).6. What is wrong with def add(item, bucket=[])?
The default list is created once at definition time and shared by every call, so items leak between calls. Use None as the default and create the list inside.7. How do you count words in a list in one line? And get the top 3?
Counter(words) and Counter(words).most_common(3). Mention that most_common(k) uses a heap internally, O(n log k).8. What is the difference between d[k], d.get(k) and defaultdict?
d[k] raises KeyError if missing. d.get(k, default) returns the default without inserting. defaultdict(int)[k] inserts and returns the default, which is what you want when counting but can pollute the dict if you use it for a read-only check.9. Why avoid building a string with += in a loop?
Strings are immutable, so each += can copy the whole string: O(n²) total. Append pieces to a list and ''.join(parts) once, O(n).10. How do you read a 20 GB log file in Python?
Open it withwith open(...) and iterate for line in f. The file object is a lazy iterator, so only one line is in memory at a time. Keep aggregate state small (a dict keyed by the thing you count), and stream the output too if it could be large.Traps
- Forgetting that
split(',')leaves the newline on the last field.strip()the line first. range(len(x))when you also need the value: useenumerate.- Comparing a
strdigit to anint:'5' == 5is False. Convert explicitly withint()and catchValueErrorfor bad input. - Using a list as a dict key or set member →
TypeError: unhashable. Convert to a tuple. max(d)gives the largest key;max(d, key=d.get)gives the key with the largest value.- Integer division:
7 / 2is3.5,7 // 2is3.-7 // 2is-4(floors), not -3. Noneis falsy, soif result:drops a legitimate0. Writeif result is not None:when 0 is valid.
Do before marking this topic done
In a blank editor, no autocomplete, write these from memory and run them on tiny inputs:
- Read lines of
host,statusfrom a string split on newlines, countDOWNper host, print hosts sorted by count descending then name. - Return the first element that appears exactly once in a list, in O(n).
- Given a list of (start, end) tuples, return them sorted by start, and say what the sort costs.
- Implement a FIFO queue with
dequeand a stack with a list, pushing 3 and popping 3 from each. - Return the k largest numbers from a list with
heapq.nlargest, then without it using a size-k min-heap.
Then do the three warm-up problems below. They are trivial by design: the goal is zero syntax hesitation.
Practice: linked problems
Logged attempts feed the tracker. Open the problem in a new tab, solve in a blank editor, then log honestly.
- LC 228Summary RangesEasy
- LC 412Fizz BuzzEasy
- LC 1365How Many Numbers Are Smaller Than the Current NumberEasy
- LC 1480Running Sum of 1d ArrayEasy
- LC 1768Merge Strings AlternatelyEasy